Чи запах коду має інтерфейс лише з геттерами?


10

(Я бачив це запитання , але перша відповідь стосується властивостей авто більше, ніж про дизайн, а друга говорить про приховування коду зберігання даних від споживача. Я не впевнений, що я хочу / мій код робить, тому я хотів би почути якусь іншу думку)

У мене є два дуже схожих об'єктів, HolidayDiscountі RentalDiscount, які представляють довжини знижки , як "якщо вона триває , по крайней мере numberOfDays, percentзнижка застосовується. У таблицях є fks для різних материнських об'єктів, і вони використовуються в різних місцях, але там, де вони використовуються, існує загальна логіка, щоб отримати максимально можливу знижку. Наприклад, у А HolidayOfferє кількість HolidayDiscounts, і при обчисленні його вартості нам потрібно розібратися в застосовній знижці. Те саме для оренди та RentalDiscounts.

Оскільки логіка однакова, я хочу тримати її в одному місці. Ось що роблять наступний метод, предикат і порівняльник:

Optional<LengthDiscount> getMaxApplicableLengthDiscount(List<LengthDiscount> discounts, int daysToStay) {
    if (discounts.isEmpty()) {
        return Optional.empty();
    }
    return discounts.stream()
            .filter(new DiscountIsApplicablePredicate(daysToStay))
            .max(new DiscountMinDaysComparator());
}

public class DiscountIsApplicablePredicate implements Predicate<LengthDiscount> {

    private final long daysToStay;

    public DiscountIsApplicablePredicate(long daysToStay) {
        this.daysToStay = daysToStay;
    }

    @Override
    public boolean test(LengthDiscount discount) {
        return daysToStay >= discount.getNumberOfDays();
    }
}

public class DiscountMinDaysComparator implements Comparator<LengthDiscount> {

    @Override
    public int compare(LengthDiscount d1, LengthDiscount d2) {
        return d1.getNumberOfDays().compareTo(d2.getNumberOfDays());
    }
}

Оскільки потрібною є лише інформація про кількість днів, я закінчую інтерфейс як

public interface LengthDiscount {

    Integer getNumberOfDays();
}

І дві сутності

@Entity
@Table(name = "holidayDiscounts")
@Setter
public class HolidayDiscount implements LengthDiscount {

    private BigInteger percent;

    private Integer numberOfDays;

    public BigInteger getPercent() {
        return percent;
    }

    @Override
    public Integer getNumberOfDays() {
        return numberOfDays;
    }

}

@Entity
@Table(name = "rentalDiscounts")
@Setter
public class RentalDiscount implements LengthDiscount {

    private BigInteger percent;

    private Integer numberOfDays;

    public BigInteger getPercent() {
        return percent;
    }

    @Override
    public Integer getNumberOfDays() {
        return numberOfDays;
    }
}

В інтерфейсі є один метод отримання, який реалізують два об'єкти, що, звичайно, працює, але я сумніваюся, що це хороший дизайн. Він не представляє жодної поведінки, якщо утримувати значення не є властивістю. Це досить простий випадок, у мене є ще пара подібних, складніших справ (з 3-4 дітьми).

Моє запитання: це погана конструкція? Який кращий підхід?


4
Мета інтерфейсу - встановити схему зв'язку, а не представляти поведінку. Цей обов'язок покладається на методи впровадження.
Роберт Харві

Що стосується вашої реалізації, існує дуже багато шаблонів (код, який насправді нічого не робить, крім структури структури). Ви впевнені, що вам все це потрібно?
Роберт Харві

Навіть методи інтерфейсу є лише "getters", це не означає, що ви не можете реалізувати "setters" для реалізації (якщо у всіх є сеттер, ви все ще можете використовувати конспект)
рюффп

Відповіді:


5

Я б сказав, що ваш дизайн трохи помилковий.

Одним із можливих рішень було б створити інтерфейс, який називається IDiscountCalculator.

decimal CalculateDiscount();

Тепер будь-який клас, якому потрібно надати знижку, реалізував би цей інтерфейс. Якщо знижка використовує кількість днів, то нехай інтерфейс насправді не цікавить, оскільки це деталі реалізації.

Кількість днів, ймовірно, належить до якогось абстрактного базового класу, якщо всі дисконтні класи потребують цього члена даних. Якщо це специфічний для класу, то просто оголосіть властивість, до якої можна отримати доступ публічно або приватно, залежно від потреби.


1
Я не впевнений, що я повністю згоден з використанням HolidayDiscountта RentalDiscountреалізацією IDiscountCalculator, оскільки вони не є калькуляторами знижок. Розрахунок проводиться тим іншим методом getMaxApplicableLengthDiscount. HolidayDiscountі RentalDiscountє знижками. Знижка не обчислюється, це тип застосованої знижки, що розраховується, або просто обрана серед ряду знижок.
користувач3748908

1
Можливо, інтерфейс слід назвати IDiscountable. Які б класи не мали змоги реалізувати. Якщо дисконтні класи для відпустки / оренди - це лише DTO / POCO і зберігають результат, а не обчислюють, це добре.
Джон Рейнор

3

Щоб відповісти на ваше запитання: ви не можете укласти запах коду, якщо в інтерфейсі є лише геттери.

Прикладом нечіткої метрики для кодових запахів є роздуті інтерфейси з багатьма методами (не в тому випадку, якщо отримувачі чи ні). Вони, як правило, порушують принцип сегментації інтерфейсу.

На семантичному рівні також не слід порушувати принцип єдиної відповідальності. Я, як правило, порушується, якщо ви бачите кілька різних проблем, що розглядаються в договорі інтерфейсу, які не є однією темою. Іноді це важко визначити, і для його ідентифікації потрібен певний досвід.

З іншого боку, ви повинні знати, якщо ваш інтерфейс визначає сетери. Це тому, що сетери, швидше за все, змінять стан, це їхня робота. Питання, яке потрібно задати тут: чи об’єкт, що знаходиться за інтерфейсом, здійснює перехід з дійсного стану в інший дійсний стан. Але це в основному не проблема інтерфейсу, а реалізація договору про інтерфейс об'єкта, що реалізує.

Єдине занепокоєння, яке я маю з вашим підходом, - це те, що ви працюєте на OR-Mappings. У цьому маленькому випадку це не проблема. Технічно це нормально. Але я б не оперував об’єктами АБО-Кап. У них може бути занадто багато різних станів, які ви точно не враховуєте в операціях, виконаних над ними ...

OR-Mapped об'єкти можуть бути (припустимо JPA) тимчасовими, стійкими від'єднаними незмінними, стійкими доданими незмінними, стійкими від'єднаними зміненими, стійкими доданими зміненими ... якщо ви використовуєте валідацію bean для цих об'єктів, ви більше не можете побачити, перевіряли чи ні об'єкт. Зрештою, ви можете ідентифікувати більше 10 різних станів, які може мати об'єкт OR-Mapped. Чи всі ваші операції справляються з цими станами належним чином?

Це, звичайно, не є проблемою, якщо ваш інтерфейс визначає контракт, а впровадження слідує за ним. Але для об'єктів, які відображають АБО, я сумніваюся, чи можна виконати такий договір.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.