純粋なDataオブジェクトより少し多いクラスがあるとしましょう。つまり、いくつかのデータとそのデータに関するいくつかの基本的なクエリを保持しています。 (この場合、時刻表を表すクラスです)
ここで、そのDataオブジェクトに対して特定の検証チェックを実行したいと考えています。これは、タイムテーブルの表現とは別の責任であると感じています。したがって、クラスScheduleValidator
またはその程度の何かがあるはずです。
ただし、バリデーターはもちろん、スケジュールが有効かどうかをいくつかのルールに従って判断するために、タイムテーブルクラスから多くのことをクエリします(特定のクラッシュのチェック、特定のキャパシティーのオーバーロードなど)。これは羨望の的と見なされますか?そうでない場合、なぜそうではないのですか?もしそうなら、それについて何をしますか?
これはうらやましい機能ではありませんが、疑わしいデザインです。
Feature envy は、あるクラスのメソッドが別のクラスのデータを使用していることを意味します "excessively "。これは、設計上の問題の兆候です。そのメソッドは他のクラスに属している必要があります。または、そのメソッドは他のクラスが提供する抽象化を必要に応じて使用しません。
あなたのデザインは純粋なデータオブジェクトを使用することを意図しています。そのため、ScheduleValidator
には他に使用する抽象化がなく、壊れるカプセル化がなく、おそらくデータ自体もありません。つまり、envyではなく、necessity 。
本当の問題は、なぜ純粋なデータオブジェクトが必要なのかということです。これは 貧血ドメインモデル のように見えます。これはOOアンチパターンですそれは 何を分離しようとするかOO団結しようとする 。
純粋なDataオブジェクトより少しだけ多いクラスがあるとしましょう...
「data object」のようなものはないので、それは良いことです。データオブジェクトは、recordまたはstructです。少なくともオブジェクト指向環境では、これらはそれ自体が悪い設計の良い兆候です。
カプセル化に対する私たちの違反または単なる違反を指摘すると、必然的にいずれかの機能羨望を引き起こします。両方または何か他のもの(たとえば Law of Demeter )に違反している正確な定義に依存しますが、要点は、オブジェクト指向と基本的に互換性がないため、何らかの規則違反されます。
外部から一部のオブジェクトを検証することは、オブジェクトの内部についての詳細な知識がないと不可能です(ご指摘のとおり)。オブジェクトが変更されるたびに、バリデーターを変更する必要がある可能性が非常に高くなります。つまり一緒に変化するものは一緒ではありません、これはメンテナンスに悪いです。
解決策は、タイムテーブルを「表す」ことではなく、TimeTable
をbeすることです。データを非表示にし、関連するビジネス機能を提供します!
私の意見や提案をする前に、前のケースからの定義を理解することをお勧めします。
Methods that make extensive use of another class may belong in another class. Consider moving this method to the class it is so envious of.
https://blog.codinghorror.com/code-smells/
この引用を念頭に置き、提案の簡単なロジックを例にまとめると仮定します。
TimeTable.Java
public class TimeTable{
private int time1;
private int time2;
private List<Location> locations = new ArrayList<>();
//getters
}
SchedulerValidator.Java
public class ScheduleValidator{
public List<Location> getLocationsByTime(int time){
//some logic
}
public List<Location> getLocationsByTime(int begin, int end){
//some logic
}
}
上記の任意の形式のスニペットは、SRP違反に対するの上にあるFeatureEnvyの素晴らしい例です。
以下の解決策はすべての基準を満たし、清潔で不満を解消します。
public class TimeTable{
private final int time1;
private final int time2;
private List<Location> locations = new ArrayList<>();
//constructor
public List<Location> getLocationsByTime(int time){
//some logic
}
public List<Location> getLocationsByTime(int begin, int end){
//some logic
}
//getters
}
public class ScheduleValidator{
public void validate(int timeValue1, int timeValue2, TimeTable timeTable){
//some validation against user inputs, in case throws an exception
}
}
public class ScheduleChecker(){
public void check(int userProvidedTime1, int userProvidedTime2, TimeTable timeTable){
//some validation against the time table list and user inputs
}
}
//I assume this is where you will use all
public class ScheduleController{
private final ScheduleValidator scheduleValidator;
private final ScheduleChecker scheduleChecker;
private final TimeTableRepository timeTableRepository;
//constructor
public String saveSchedule(int time1, int time2){
TimeTable timeTable = timeTableRepository.find(1L);
scheduleValidator.validate(time1, time2, timeTable);
scheduleChecker.check(time1, time2, timeTable);
//no exceptions further business logic carries on after this line
}
}
この例もテスト可能であり、単純にすべてを簡単にモックして、単体テストと統合テストを作成できます。
最後に、私の提案は、要件を壊すことに取り組むことです: