多くのタイプがある場合、オブジェクトのタイプでサブクラス化するべきではありませんか?
-
02-07-2019 - |
質問
約60の異なる「タイプ」があるイベントのログを処理しています。イベントの。各イベントは約10個のプロパティを共有し、その後、さまざまな追加プロパティを共有するイベントのサブカテゴリがあります。
これらのイベントをどのように扱うかは、それらのタイプまたは実装するカテゴリインターフェイスによって異なります。
しかし、コードが肥大化しているようです。サブクラスメソッドは同じインターフェイスのいくつかを実装しているため、多くの冗長性があります。
" type"を持つ単一のイベントクラスを使用する方が適切ですか?タイプをチェックし、タイプのカテゴリの組織を維持するプロパティと書き込みロジック(たとえば、カテゴリaであるイベントタイプのリスト、カテゴリbである2番目のリストなど)または、この場合、サブクラスの設計はより適切ですか?
最初のアプローチ:
public interface Category1 {}
public interface Category2 {}
public abstract class Event {
private base properties...;
}
public class EventType1 extends Event implements Category1, Category2 {
private extra properties ...;
}
public class EventType2 extends Event implements Category3, Category4 {
private extra properties ...;
}
2番目のアプローチ:
public enum EventType {TYPE1, TYPE2, TYPE3, ...}
public class Event {
private union of all possible properties;
private EventType type;
}
個人的な意見では、単一のイベントオブジェクトが適切であるように思われます。なぜなら、私がそれについて正しく考えている場合、モデルを表すために継承を使用する必要はないからです。タイプに基づいて変化する条件。
次のようなコードが必要です:
if(event instanceof Category1) {
...
}
これは、instanceofの代わりに、イベントでメソッドを呼び出して「同じコード」を実装できるという点で、最初のアプローチでうまく機能します。適切な各サブクラスで。
しかし、2番目のアプローチは非常に簡潔です。次に、次のように書きます:
if(CATEGORY1_TYPES.contains(event.getEventType()) {
...
}
そして、すべての「処理ロジック」単一のクラスに編成することができ、サブクラス間で重複して広がることはありません。だから、これはオブジェクト指向がより適切に見えますが、あまり良くない場合ですか?
解決
イベントのタイプごとに、イベント自体が実行できる動作が本質的に異なるかどうかによって異なります。
イベントオブジェクトには、タイプごとに異なる動作をするメソッドが必要ですか?その場合、継承を使用します。
そうでない場合は、enumを使用してイベントタイプを分類します。
他のヒント
イベントタイプごとのオブジェクトソリューションを使用しますが、代わりにスケルトン実装を提供する(おそらく抽象的な)クラスの下で、一般的に使用されるインターフェイスの組み合わせをグループ化します。これにより、多くのインターフェイスを使用することで生成されるコードの肥大化は大幅に削減されますが、一方でクラスの数は増加します。ただし、適切かつ合理的に使用すると、コードが簡潔になります。
継承の抽象ベースクラスを拡張する場合、 特定のCategoryインターフェイス。別のCategoryも実装する必要がある場合があるためです。
だから、ここに提案されたアプローチがあります: 特定のCategoryインターフェイスメソッド(イベントに関係なく)に同じ実装が必要であると仮定すると、Categoryインターフェイスごとに実装クラスを作成できます。
つまり、次のようになります:
public class Category1Impl implements Category1 {
...
}
public class Category2Impl implements Category2 {
...
}
各イベントクラスに対して、実装するCategoryインターフェイスを指定し、Category実装クラスのプライベートメンバーインスタンスを保持します(したがって、継承ではなく構成を使用します)。 Categoryインターフェースメソッドごとに、メソッド呼び出しをCategory実装クラスに転送するだけです。
探していた答えが実際には得られなかったので、望ましくない学習経験に基づいて独自の最良の推測を提供しています。
イベント自体には実際にはビヘイビアはありません。ビヘイビアがあるのはイベントのハンドラです。イベントはデータモデルを表しているだけです。
Javaの新しい変数引数とオートボクシング機能を使用できるように、イベントをプロパティのオブジェクト配列として扱うようにコードを書き直しました。
この変更により、約100の巨大なコードクラスを削除し、1つのクラスの約10行のコードで同じロジックの多くを達成することができました。
教訓:OOパラダイムをデータモデルに適用することは必ずしも賢明ではありません。大規模な可変ドメインで作業する場合、OOを介して完璧なデータモデルを提供することに集中しないでください。オブジェクト指向の設計は、モデルよりもコントローラーの方が有利な場合があります。通常、10%のパフォーマンスの低下は許容範囲であり、他の手段で回復できるため、事前に最適化に集中しないでください。
基本的に、私は問題を過剰に設計していました。これは、適切なオブジェクト指向設計が過剰であり、一晩のプロジェクトを3か月のプロジェクトに変えるケースであることがわかります。もちろん、私は難しいことを学ばなければなりません!
多数の.javaファイルが存在するだけでは、必ずしも悪いことではありません。クラスのコントラクトを表す少数(2〜4程度)のインターフェイスを意味のある形で抽出し、すべての実装をパッケージ化できる場合、提示するAPIは、60の実装でも非常にきれいになります。
また、いくつかのデリゲートまたは抽象クラスを使用して、共通の機能を取り込むことをお勧めします。デリゲートおよび/または抽象ヘルパーは、すべてパッケージプライベートまたはクラスプライベートである必要があり、公開するAPIの外部では使用できません。
振る舞いがかなり混ざり合っている場合は、他のオブジェクトの構成を使用することを検討し、特定のイベントタイプオブジェクトのコンストラクターでそれらのオブジェクトを作成するか、ビルダーを使用してオブジェクトを作成します。
おそらくこのようなものですか?
class EventType {
protected EventPropertyHandler handler;
public EventType(EventPropertyHandler h) {
handler = h;
}
void handleEvent(map<String,String> properties) {
handler.handle(properties);
}
}
abstract class EventPropertyHandler {
abstract void handle(map<String, String> properties);
}
class SomeHandler extends EventPropertyHandler {
void handle(map<String, String> properties) {
String value = properties.get("somekey");
// do something with value..
}
}
class EventBuilder {
public static EventType buildSomeEventType() {
//
EventType e = new EventType( new SomeHandler() );
}
}
おそらくいくつかの改善が行われる可能性がありますが、それがあなたを始めるかもしれません。