web-dev-qa-db-ja.com

ゲッターのみのインターフェースはコードの匂いですか?

(私は この質問 を見てきましたが、最初の答えは設計よりも自動プロパティについてであり、2番目の答えは消費者からのデータストレージコードを非表示にします、これは私が欲しい/私のコードが何をするのかわからないので、他の意見を聞きたいです)

非常によく似た2つのエンティティHolidayDiscountRentalDiscountがあります。これらは、長さの割引を「少なくともnumberOfDaysが持続する場合は、percent割引が適用される」と表しています。テーブルにはさまざまな親エンティティへのアクセス権があり、さまざまな場所で使用されますが、それらが使用される場所には、適用可能な最大の割引を取得するための共通のロジックがあります。たとえば、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;
    }
}

このインターフェースには、2つのエンティティーが実装する単一のゲッターメソッドがありますが、これはもちろん機能しますが、それが優れたデザインであるとは思えません。値を保持することはプロパティではないため、これは動作を表していません。これはかなり単純なケースです。似たような複雑なケースがさらに2つあります(3〜4ゲッターを使用)。

私の質問は、これは悪いデザインですか?より良いアプローチとは何ですか?

10
user3748908

あなたのデザインは少し見当違いです。

解決策の1つは、IDiscountCalculatorというインターフェイスを作成することです。

decimal CalculateDiscount();

これで、割引を提供する必要があるすべてのクラスがこのインターフェースを実装します。割引が日数を使用する場合、それが実際の日数である場合、それは実装の詳細であるため、インターフェイスは実際には気にしません。

すべての割引クラスがこのデータメンバーを必要とする場合、日数はおそらくある種の抽象基本クラスに属します。クラス固有の場合は、必要に応じてパブリックまたはプライベートにアクセスできるプロパティを宣言します。

5
Jon Raynor

あなたの質問に答えるために:インターフェースにゲッターしかない場合、コードの匂いを終わらせることはできません。

コードのにおいのファジーメトリックの例は、多くのメソッドを使用した膨張したインターフェイスです(ゲッターかどうかに関係なく)。それらは、インターフェース分離の原則に違反する傾向があります。

セマンティックレベルでは、単一責任の原則にも違反するべきではありません。インターフェースコントラクトで、1つの主題ではないいくつかの異なる問題が処理された場合、違反する傾向があります。これを特定するのは難しい場合があり、特定するには経験が必要です。

反対に、インターフェースがセッターを定義しているかどうかに注意する必要があります。それは、セッターが状態を変更する可能性が高いためです。ここで尋ねる質問:インターフェイスの背後にあるオブジェクトは、有効な状態から別の有効な状態に遷移しますか?しかし、それは主にインターフェースの問題ではなく、実装オブジェクトのインターフェースコントラクトの実装です。

あなたのアプローチに関して私が唯一懸念しているのは、ORマッピングを操作することです。この小さなケースでは、これは問題ではありません。技術的には大丈夫です。しかし、私はORマップオブジェクトを操作しません。彼らはあなたが確かにそれらで行われた操作で考慮しないあまりにも多くの異なる状態を持つことができます...

ORマッピングされたオブジェクトは、(JPAを前提として)一時的、永続的なデタッチ未変更、永続的なアタッチ未変更、永続的なデタッチ変更、永続的なアタッチ変更...などのオブジェクトでBean Validationを使用すると、オブジェクトがチェックされているかどうかを確認できません。結局のところ、ORマップされたオブジェクトが持つことができる10を超える異なる状態を特定できます。すべてのオペレーションがこれらの状態を適切に処理していますか?

インターフェースがコントラクトを定義し、実装がそれに従う場合、これは確かに問題ありません。しかし、ORマップされたオブジェクトの場合、そのような契約を完全に満たすことができるかどうかは、私には合理的な疑いがあります。

3
oopexpert