web-dev-qa-db-ja.com

基本クラスのサブタイプであるイベントを適用する単一のジェネリックメソッドを定義するにはどうすればよいですか?

:質問を適切に構成する方法がわからないので、自由に編集するか、改善方法についてコメントしてください。)

この質問はかなり広い範囲をカバーしていますが、静的に型付けされた(オブジェクト指向)言語(特にJava=)を使用してCQRSおよびイベントソーシングを実装しています)にかなり限定されていると思います。特定の集約に関連するイベントを階層として設計しました。それらは1つの親クラスのサブクラスです。Order集約があり、サブタイプがOrderEventの基本クラスがあるとします。 OrderPlacedQuantityUpdatedOrderCancelledなどの特定のイベント.

さて、このOrder集合体の中で、apply(OrderCancelled)apply(QuantityUpdated)のようなapplyの特定のサブタイプを受け取るOrderEventメソッドをオーバーロードしました。ただし、イベントソースからOrderオブジェクトを再構成すると、宣言されたインターフェイスがOrderEventであるオブジェクトのリストが表示されます。技術的には、基礎となるシステムは、それらのオブジェクトのそれぞれがどの特定のサブタイプであるかを追跡しますが、コード内で、各タイプにapplyメソッドを具体的に提供したことを示す方法はありません。親OrderEventクラスを取るメソッドを定義する必要があります:apply(OrderEvent)

この周りに私が知らないパターンが存在しますか?または、次のような唯一のオプションです(Javaで記述)。

void apply(OrderEvent event) {
    if (event instanceof OrderPlaced) {
        apply((OrderPlaced) event);
    }
    if (event instanceof QuantityUpdated) {
        apply((QuantityUpdated) event);
    }
    if (event instanceof OrderCancelled) {
        apply((OrderCancelled) event);
    }
}

特定のapplyToオブジェクトが適用されるOrderEventオブジェクトを受け取るOrder親クラス/インターフェースでOrderEventメソッドを定義することを検討しました。例えば:

class OrderPlaced extends OrderEvent {
    void applyTo(Order order) {
        order.apply(this);
    }
}

これにより、Orderクラスでapply(OrderEvent)を次のように定義できます。

void apply(OrderEvent event) {
    event.applyTo(this);
}

その結果、コードはより簡潔になりますが、アプローチのほぼ循環的な性質についてはよくわかりません。

これに使用される一般的なパターンは何ですか?

7
Psycho Punch

これは this SO question のように、戦略に渡される「コンテキスト」の追加パラメーターを含む strategy pattern です。結果はOrderEventOrderの間の緊密な双方向カップリング(この場合は問題ない場合があります)。各OrderEventオブジェクトが常に同じOrderオブジェクトに関連付けられている場合は、thisOrderオブジェクト)をコンストラクターパラメーターとして関連するOrderEventに渡してください。その場合、applyToメソッドにパラメーターを保存できます(ただし、カップリングは残ります)。同じOrderEventを異なるオーダーに適用できる場合、ソリューションは問題ありません。

PS ::これはDDD、CQRS、またはイベントソーシングに特別なことではないので、質問タグが適切に適合しないと思います。

3
Doc Brown

残念ながら、Javaは マルチディスパッチ)をサポートしていません ですが、リフレクションを使用して、Openクローズの原則を壊したり、イベントと集計の間に偶発的な依存関係を導入したりせずに、適切なメソッドを呼び出すことができます。

したがって、正しいdispatcherメソッドを識別するapplyを使用して、それを呼び出すことができます。

class Dispatcher {
   public void applyEvent(Object aggregate, Object event) {
      Method m = aggregate.getClass().getDeclaredMethod('apply',new Class[](event.getClass());
      m.invoke(aggregate,new Object[](event);
   }
}

このコンポーネントをどのように使用するかはあなた次第です(たとえば、Aggregateルートがコンポーネントから継承する基本クラスになるようにコンポーネントを適合させることができます)。

3

ここであなたのニーズのコンテキストを誤って理解した場合は失礼しますが、私が理解しているのは、それが構築されたサブクラスに基づいて、各イベントに異なる実装を適用する必要があるということです。一般に、instance ofに頼る必要があり、たとえば各サブクラスのapplyメソッドをオーバーロードすると、何か間違ったことをしたことになるため、デザインボードに戻ることになります。

私の意見では、すべてのデリゲートオーダーイベントオブジェクトに必要なジェネリックメソッドシグネチャ(apply、do、get、setなど)、インターフェイスまたは上記のメソッドを抽象化したスーパークラス(サブクラスにそれらを実装させる) )。次に、これをすべてのOrderEventサブクラスに実装して、必要な動作を区別します。次に、インターフェイスまたはスーパークラスオブジェクトを引数として持つ単一の適用メソッドの問題であり、適用メソッド内から適切なジェネリックメソッドを呼び出すだけで、コンパイラは実行時にどのタイプのオブジェクトを持っているかを認識し、適切なメソッドを呼び出しますそのため。

これを少し視覚化するには、これを検討してください:

interface OrderEvent{
(abstract)do();get();set();
}
class OrderCancelled implements or extends OrderEvent{
do(){ logic;} get(){logic;} set(){logic;}
}
class CompositeOrderWithDelegate(){
 apply(OrderEvent oe){
  oe.do();
 ...
 }
}
2
Leon

あなたの2番目のアプローチは良いですが、現時点ではこれが循環につながります。あなたがしなければならないことは、実際のイベントロジックをイベントに入れることです。

今あなたはします

class OrderPlaced extends OrderEvent {
    void applyTo(Order order) {
        order.apply(this);
    }
}

何をすべきか

class OrderPlaced extends OrderEvent {
    void applyTo(Order order) {
        //real logic what happens in order class happens here now
        order.setDate(date);
        order.doThis(var1);
        order.doThat(var2);
    }
}

したがって、OrderEventsだけがそれらがどのようにOrderに適用されるかを知っており、Orderクラスは何も知りません。これで、もう一度Orderクラスに触れることなく、好きなだけOrderEventサブクラスを作成できます。

0
ElDuderino

これに使用される一般的なパターンは何ですか?

使用されている主なパターンの両方を実際に発見しました。

その結果、コードはより簡潔になりますが、アプローチのほぼ循環的な性質についてはよくわかりません。

はい、それはコードのにおいです。

つまり、これらのイベントはすべて共通のルートを拡張するという決定を下しました。しかし、その後、イベントに関するその決定がOrderに漏れることになります。それはあまり良くありません。

モジュール内の決定を非表示にする は、コードへの変更を容易にする重要な部分です。後で自由に変更できるように、決定に密接な場所の数を最小限に抑えたいと考えています。

以下のデザインは、あなたが今やっていることに似ています

class API {
    interface OrderEvent {
        void applyTo(Order order);
    }
    interface OrderPlaced extends OrderEvent {
        // ...
    }
}
class Data {
    class OrderPlaced implements API.OrderPlaced {
        public void applyTo(Order order) {

        }
    }
}

しかし、それはあなたの懸念を混乱させます。 OrderPlacedについて知る必要がある人は、applyToについても知る必要があります。これは、その情報を何にも使用しないため意味がありません。

と比べて:

class API {
    interface OrderPlaced {
        // ...
    }
}
class History {
    interface OrderEvent {
        void applyTo(Order order);
    }
}
class Data {
    class OrderPlaced implements API.OrderPlaced, History.OrderEvent {
        public void applyTo(Order order) {

        }
    }
}

注文イベントの異種のコレクションを認識するコードは、History.OrderEventインターフェイスを必要としますが、OrderPlacedや実装の詳細について何も知る必要はありません。 OrderはAPI.OrderPlacedについて知っている必要がありますが、注文が汎用的であるという事実については何もしません。

もちろん、実装(クラス自体、そのコンストラクターを呼び出すファクトリー)に直接結合するものはすべて、真実全体を知る必要があります。

0
VoiceOfUnreason