Интерфейс с только получателями - это запах кода?


10

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

У меня есть две очень похожие сущности, HolidayDiscountи они RentalDiscountпредставляют скидки на длину как «если это длится, по крайней мере numberOfDays, percentскидка применима». Таблицы имеют fks для различных родительских объектов и используются в разных местах, но там, где они используются, существует общая логика для получения максимальной применимой скидки. Например, у a 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
Цель интерфейса - установить шаблон соединения, а не представлять поведение. Эта обязанность относится к методам реализации.
Роберт Харви

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

Методы Даже интерфейс являются только «добытчиками» , это не означает , что вы не можете Реализует «сеттеры» по реализации (если все они имеют сеттера, с вы все еще можете использовать абстрактное)
рüффп

Ответы:


5

Я бы сказал, что ваш дизайн немного ошибочен.

Одним из возможных решений было бы создание интерфейса под названием IDiscountCalculator.

decimal CalculateDiscount();

Теперь любой класс, которому необходимо предоставить скидку, будет реализовывать этот интерфейс. Если скидка использует количество дней, так или иначе, интерфейс на самом деле не заботится, поскольку это детали реализации.

Число дней, вероятно, принадлежит некоторому абстрактному базовому классу, если этот класс данных нужен всем классам скидок. Если это зависит от класса, тогда просто объявите свойство, к которому можно получить доступ публично или приватно в зависимости от необходимости.


1
Я не уверен, что полностью согласен с наличием HolidayDiscountи RentalDiscountвнедрением IDiscountCalculator, потому что они не калькуляторы скидок. Расчет производится другим способом getMaxApplicableLengthDiscount. HolidayDiscountи RentalDiscountскидки. Скидка не рассчитывается, это тип применяемой скидки, которая рассчитывается или просто выбирается из числа скидок.
user3748908

1
Возможно, интерфейс должен называться IDiscountable. Любые классы, которые нужно реализовать, могут. Если классы праздничных / арендных скидок являются просто DTO / POCO и сохраняют результат, а не рассчитывают, это нормально.
Джон Рейнор

3

Чтобы ответить на ваш вопрос: вы не можете сделать вывод о запахе кода, если в интерфейсе есть только геттеры.

Примером нечеткой метрики для запахов кода являются раздутые интерфейсы со многими методами (не с геттерами или без). Они имеют тенденцию нарушать принцип сегрегации интерфейса.

На семантическом уровне принцип единой ответственности также не должен нарушаться. Я склонен нарушаться, если в контракте на интерфейс вы видите несколько разных проблем, которые не относятся к одной теме. Иногда это трудно определить, и вам нужен некоторый опыт, чтобы определить это.

С другой стороны, вы должны знать, если ваш интерфейс определяет сеттеры. Это потому, что сеттеры могут изменить состояние, это их работа. Вопрос, который нужно задать здесь: выполняет ли объект за интерфейсом переход из допустимого состояния в другое допустимое состояние. Но это в основном не проблема интерфейса, а реализация контракта интерфейса объекта реализации.

Единственное, что меня беспокоит в связи с вашим подходом, это то, что вы работаете с OR-Mappings. В этом маленьком случае это не проблема. Технически это нормально. Но я не буду оперировать объектами OR-Mapped. У них может быть слишком много разных состояний, которые вы наверняка не учитываете при выполнении операций над ними ...

OR-Mapped объекты могут быть (предположительно JPA) временными, постоянными отсоединенными без изменений, постоянными прикрепленными без изменений, постоянными отсоединенными измененными, постоянными присоединенными измененными ... если вы используете проверку объектов на этих объектах, вы также не сможете увидеть, был ли объект проверен или нет. В конце концов вы можете идентифицировать более 10 различных состояний, которые может иметь объект OR-Mapped. Все ли ваши операции обрабатывают эти состояния правильно?

Это, конечно, не проблема, если ваш интерфейс определяет контракт, а реализация следует ему. Но для объектов, отображаемых в OR, у меня есть основания сомневаться в том, что они смогут выполнить такой контракт.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.