質問は主に設計に関する質問です(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 の継承階層を複製することを意味します。望ましい結果を達成するためのより良い方法はありますか? または、何か不足していますか?それとも、どういうわけかサービスをエンティティに注入した方がよいでしょうか?