2

質問は主に設計に関する質問です(dddに多少関連しています)。不自然な例で申し訳ありません:

さまざまな種類の果物 (リンゴ、チェリーなど) を表す (ドメイン) クラスがあるとします。ここで、ジュースを絞り出す動作を実装する必要があるとします。呼び出し元は、自分が獲得した特定の果物を知らなくても、スクイーズを呼び出すことができる必要があります。

この振る舞いをどこに置くべきですか?

確かに、果物のインターフェイス/基本クラス関数を定義できます

Fruit#squeeze()

すべてのサブクラスに独自の動作を実装させます。これで、呼び出し元は次のようなことを簡単に実行できます。

Fruit f = new Cherry();
f.squeeze();

しかし、絞りがそれほど単純ではなく、果物ごとに異なる外部サービスを呼び出すなどのより複雑な動作を伴う場合はどうすればよいですか

AppleJuicerService#squeeze(Apple a)

CherryJuicerService#squeeze(Cherry c)

? ドメイン クラスからサービスを呼び出すのは間違っていると感じます。

すべてのサブクラスが異なるサービスを必要とするため、ここには当てはまらない二重ディスパッチ パターンについて読みました。

私の質問は次のとおりです。「クリーンな」デザインを得るためにここで何ができるでしょうか?

編集:

これまでのすべての回答に感謝します。問題を少し明確にしてみます。ここで述べようとしている問題について、うまくいけばあまり工夫されていない別の例を挙げようと思います。

その内容を文字列として表示できる Message 基本クラスを考えてみましょう。

interface Message {
    String showContent();
}

ここで、EMailMessage のようなさまざまな種類のメッセージがあるとします。

class EMailMessage implements Message {

    //some specific parameters for email
    private EmailAddress recipientEmail;

    public String showContent() {
        //here the content would be converted to string
        return "the content of an EMail" 
    }
}

別のタイプは SMSMessage です。

class SMSMessage implement SMSMessage {

    //some specific parameters for SMS
    private TelNumber recepientTelephoneNumber;

    public String showContent() {
        //here the content would be converted to string
        return "the content of a SMS"
    }
}

さらに、メッセージがエンティティとしてモデル化されているため、データベースに永続化できるとします。かなり技術的ではありますが、Spring などの依存性注入フレームワークを使用して依存性を注入するとします。

果物の例と同様に、メッセージを受信者に送信する send() 動作を実装する必要があると考えてください。さらに、電子メールの送信には SMS とは異なるロジックが含まれるとします。ここで、質問: メッセージを送信するロジックをどこに配置する必要がありますか?

通常、SMS サービス プロバイダーの API などをカプセル化する、たとえば SMS を送信するためのサービスを作成することを選択します。さらに、メールの送信をカプセル化する別のサービスを作成します。

interface SendMessageService<T extends Message> {
    void send(T message);
}

class SendEmailService extends SendMessageService<EMailMessage> {
    public void send(EMailMessage message) {
        //send the EMail
    }
}

class SendSMSService extends SendMessageService<SMSMessage> {
    public void send(SMSMessage message) {
        //send the SMS
    }
}

このアプローチの欠点は、具体的なサブクラスを決定せずにメッセージを送信できないことです。つまり、次のようなことは直接可能ではありません。

List<Message> messages = //Messages of different types 

SendMessageService service = //???

for (Message m : messages) {
    service.send(m);
}

確かに、特定のタイプのメッセージに従ってサービスを作成するためのファクトリを作成できます。しかし、これは Message の継承階層を複製することを意味します。望ましい結果を達成するためのより良い方法はありますか? または、何か不足していますか?それとも、どういうわけかサービスをエンティティに注入した方がよいでしょうか?

4

3 に答える 3

1

作業をSqueezeBehaviorインターフェイスに委譲し、各実装でsqueezeFruitまたは特定のFruit. これは未加工のアイデアです (改善できることを意味しますが、最初のステップとしては適切です)。

interface SqueezeBehavior<T> {
    void squeeze(T squeezeMe);
}

interface FruitSqueezeBehavior<T extends Fruit> extends SqueezeBehavior<T> {
}

class FruitSqueezer implements FruitSqueezeBehavior<Fruit> {
    public void squeeze(Fruit fruit) {
        System.out.println("squizing any fruit");
    }
}

class AppleSqueezer implements FruitSqueezeBehavior<Apple> {
    public void squeeze(Apple apple) {
        System.out.println("squizing apple");
    }
}

class CherrySqueezer implements FruitSqueezeBehavior<Cherry> {
    public void squeeze(Cherry cherry) {
        System.out.println("squizing cherry");
    }
}

class FruitService {

    public void foo(Fruit fruit) {
        FruitSqueezeBehavior fruitSqueezer = ...
        fruitSqueezer.squeeze(fruit);
    }
}
于 2013-09-25T21:10:42.683 に答える
0

DomainEvents を検討してください。これにより、ドメイン モデルを外部サービスから切り離すことができます (通常、ステートレス Bean を注入する必要があります)。

interface Fruit {
    void squeeze();
}

class Apple implements Fruit {

    @Override
    public void squeeze(){
        // domain rules validations
        DomainEvents.raise(new AppleSequeezedEvent(this));
    }
}

class Cherry extends Fruit {
    @Override
    public void squeeze(){
        // domain rules validations
        DomainEvents.raise(new CherrySequeezedEvent(this));
    }
}

class Banana extends Fruit {
    @Override
    public void squeeze(){
        // domain rules validations
        // hmm...No one cares banana...
    }
}

class DomainEvents {
    private static List<DomainEventHandler> handlers = new ArrayList<DomainEventHandler>();

    public static void register(DomainEventHandler handler) {
        this.handler.add(handler);
    }

    public static void raise(DomainEvent event) {
        for (DomainEventHander handler: handlers) {
            if (handler.subscribe(event.getClass()) {
                 handler.handle(event);
            }
        }
    }
}

これで、apple をテストするときに、ハンドラーのモック/スタブを登録できます。

@Test
public void tellsAppleIsSqueezed() throws Throwable {
    DomainEventHandler stub = new FruitSqueezedEventHandlerStub(Apple.class);
    DomainEvents.register(stub );

    Apple apple = new Apple();

    apple.squeeze();

    //assert state change of apple if any before you publishing the event
    assertThat(stub.getSqueezed(), sameInstance(apple));
}

独自の単体テスト ケースで実際のハンドラーをテストできます。

しかし、このソリューションはさらに複雑になると思います。

于 2013-09-26T02:02:21.317 に答える