web-dev-qa-db-ja.com

コマンドデザインパターンは、クラスの依存関係の数を減らすための良い方法ですか?

最近、多くのクラスで依存関係が多すぎることに気付きました。今私は最も重要なクラスのためにそれを解決しようとしています。どのようにアイデアがあるかはわかりますが、それが最良のアイデアかどうかはわかりません。

最も重要なクラスはServiceクラスです。これは、アプリ内の1つの「もの」をさまざまな状態に移行させることです。ユーザーがアプリで特定のアクションを実行することによってトリガーされる特定のフローがあります。 (送信、確認、拒否、閉じる、...)

この時点で、アクションごとに、そのオブジェクトのステータスを変更するためのパブリックメソッドがあり、約10のメソッドがあります。このクラスの責任は「状態の変更」ですが、これは間違っていると感じます。

ただし、状態が変化した場合は、電子メールの送信、特定のタスクのクローズ、ドキュメントの検索などの他のアクションを実行する必要があります。これらのタスクの多くには他の異なる依存関係があり、すべてのメソッドが同じクラスにあるため、メインサービスには12の依存関係があります。

私の主な目標は、実行可能なすべてのアクションのある種の「概要」を含みながら、メインサービスの依存関係の数を減らすことです。

一般的なデザインパターンのリストを見ていたところ、「コマンド」パターンに気づきました。 「SubmitThing」、「CloseThing」のような一連のコマンドクラスを作成できると思います。それぞれに固有の依存関係があります。このようにして、依存関係を他のクラスに移動し、各アクションには独自の依存関係があります。

コマンドパターンについて私が見つけることができる情報は、実行されたコマンドを追跡し、元に戻し、ロギングを追加できるという主な利点を説明しています。パターンの説明が魅力的に聞こえても、これは本当に解決したかった問題ではありません。

では、コマンドデザインパターンはここに行く方法ですか、それともいくつかのアクションを持つクラスをより小さな部分に分割するためのより良いパターンがありますか?

小さな例:

    public class ThingService {

        private IDocumentService documentService;
        private IMailService mailService;
        private IAnotherService anotherService;
        private IActionsOnClosingService actionsOnClosingService;

        public ThingService(IDocumentService documentService,
                            IMailService mailService,
                            IAnotherService anotherService,
                            ...
                            ) {
            this.documentService = documentService;
            this.mailService = mailService;
            this.anotherService = anotherService;
            ...
        }

        public void submitThing(Thing thing, SubmitData data) {
            thing.setStatus(SUBMITTED);
            thing.setData(data);
            documentService.doSomething();
            mailService.doSomething();
        }

        public void closeThing(Thing thing, CloseData data) {
            thing.setStatus(CLOSED);
            thing.setData(data);
            documentService.doSomething();
            mailService.doSomething();
            actionsOnClosingService.doActionsOnClosing();
        }
    }
7
geoffreydv

これは、イベント駆動型プログラミングといくつかのオブザーバーを使用することで解決できます。このメソッドはいくつのことをしますか?

public void submitThing(Thing thing, SubmitData data) {
    thing.setStatus(SUBMITTED);
    thing.setData(data);
    documentService.doSomething();
    mailService.doSomething();
}

三。 3つのことを行います。

  1. thingを送信します。
  2. ドキュメントサービスで何かを行います。
  3. thingが送信されたことをメール通知で送信します。

somethingが送信されたときに、もう1つのthingが必要になるとどうなりますか? わかっています!このクラスをもう一度変更します!

この設計は、単一の責任とオープンクローズの原則の両方に違反しています。このイベント駆動型設計と比較してください。 (私のC#を失礼します...)

public event EventHandler<ThingSubmittedEventArgs> ThingSubmitted;

public void submitThing(Thing thing, SubmitData data) {
    thing.setStatus(SUBMITTED);
    thing.setData(data);

    var handler = ThingSubmitted;
    if (handler != null) {
        handler(this, data);
    }
}

これで、さらに別のことを行う必要がある場合、このクラスを変更する必要はありません。 ThingSubmittedイベントをリッスンし、リスナーに適したアクションを実行します。

この設計の有益な副作用として、このクラスはMailServiceまたはDocumentServiceに依存しなくなりました。

4
RubberDuck

では、コマンドデザインパターンはここに行く方法ですか、それともいくつかのアクションを持つクラスをより小さな部分に分割するためのより良いパターンがありますか?

これは、アクションが互いにどの程度緊密に結合されているかにある程度依存します。

可能性としては、コマンドパターンのアイデアとコンポジットのアイデアを組み合わせることが考えられます。個々の独立したアクションは、独自のコマンドです。子の呼び出しを順序付ける方法を知っている複合コマンドは、コマンドグラフを編成するために使用されます。

ファクトリー、流暢なビルダーなどは、依存関係を直接結合しすぎずにコマンドグラフを作成するのに役立ちます。

別の可能性は、イベント駆動型のアプローチです。コマンドを実行すると、メッセージが発行されます。これらのメッセージのサブスクライバーは、新しいイベントをパブリッシュできるコマンドを実行し、ずっと下にタートルします。

ドメイン駆動設計では、後者の実装が非常に一般的です。個々のコマンドは、それぞれ独自の不変式を維持する責任がある単一の集約を対象としています。アグリゲートの状態が変化すると、ドメインイベントがブロードキャストされます。下流のイベントプロセッサはそれらのイベントを監視し、プロセスを実行するための追加のコマンドをディスパッチします。

すべてがうまくいくハッピーパスでは、選択肢を分けることはほとんどありません。コマンドが失敗し始めたときのユースケースに注意してください-MailServiceが利用できない場合、以前のコマンドで何が起こるはずですか? Udi Dahanは 信頼できるメッセージング についてかなり良い講演を行い、これらの懸念のいくつかについて議論しました。

2
VoiceOfUnreason