1

私は現在、コマンドが基本的にハンドラーの.Handle()メソッドの DTO である、設計中のサービスのコマンドハンドラーパターンを実装しています。さまざまな具象クラスの実装を開始すると、Open/Closed Principle および Single Responsibility Principle を満たすために、何千もの Command および Handler クラスになる可能性があることに気付きます。これは、Don't Repeat Yourself Principle に大きく違反します。

たとえば、私がカプセル化しているプロセスの一部では、ProjectId60 個の奇数テーブルからすべてのデータを削除してそれらをリセットする必要があります。それぞれをアトミックな具象 Command オブジェクトと具象 CommandHandler オブジェクトとして実装すると、この最初のステップだけで 120 個のクラスができます。 それらはすべてSRPとOCPに完全に準拠しますが、DRYは深刻な打撃を受けます...

public class DeleteProjectLogCommand : CommandBase
{
    public long? ProjectId { get; set; }
}

public class DeleteProjectLogCommandHandler : ICommandHandler<DeleteProjectLogCommand>
{
    public async Task<Feedback<DeleteProjectLogCommand>> Handle(DeleteProjectLogCommand command, CancellationToken token)
    {
        // ...
    }
}

別の方法として、単一の多目的コマンドおよびハンドラー クラスを実装しProjectTables、個別のすべてのクラスの代わりに列挙を使用することもできます。

public class DeleteTableByProjectIdCommand : CommandBase
{
    public DeleteTableByProjectIdCommand(ProjectTables table, long? projectId) {}

    public long? ProjectId { get; set; }        
    public ProjectTables Table { get; set; }
}

public class DeleteTableByProjectIdCommandHandler : ICommandHandler<DeleteTableByProjectIdCommand>
{
    public async Task<Feedback<DeleteTableByProjectIdCommand>> Handle(DeleteTableByProjectIdCommand command, CancellationToken token)
    {
        switch(command.Table)
        {
            case ProjectTables.ProjectLog:
                // x60 tables
                break;
        }
    }
}

ただし、新しいテーブルが追加された場合、列挙とそれを使用するすべての場所の両方も更新する必要があるため、これは Open/Closed Principle に違反します。60 ケースの switch ステートメントから得られる臭いは言うまでもありません。

すっごい…どっちが勝つ?DRY または SRP & OCP?

4

3 に答える 3

2

頭字語に縛られすぎないでください。正しいと感じるコードを書くことに集中してください。アトミック コマンドは非常に良いアイデアですが、適切なレベルの粒度が必要です。私は通常、コマンドは完全な (ユーザー) 操作であると考えています。

列挙型と神のスイッチの設計は、基本的な健全性テストに失敗し、クラス自体を変更しないと拡張できないため、悪いに違いありませんよね?

于 2014-04-22T15:44:57.600 に答える
0

RelayCommand の使用を検討してください: http://msdn.microsoft.com/en-us/magazine/dn237302.aspx

これは ICommand を実装するコマンドですが、実際の作業のためにデリゲートが挿入されることを期待しています。多くの MVVM フレームワークには、そのまま使用できる RelayCommand (または DelegateCommand) が含まれています。

したがって、コマンド インターフェイスを実装し、Action または Action<T> を ctor に挿入するように要求します。Execute はアクションをトリガーします。アクションに何かを渡す必要がある場合は、「ofT」バージョンを使用するか、渡すデリゲートでそれを囲むことができます。

これにより、次のことが可能になります。

  • Command を 1 つ (ジェネリックをサポートする場合は 2 つ) 実装します。
  • 実際のコマンド ロジックを別の場所に配置します (ドメイン オブジェクトなど)。
  • 理にかなっている場合、コマンド ロジックは実際にはドメイン クラスのプライベート メンバーであり、デリゲートを渡したためにコマンドによって公開されます。

例:

public class SomeViewModelOrDomainClass
{
  public ICommand DoItCommand {get; private set;}

  //ctor
  public SomeViewModelOrDomainClass()
  {
    // if your command includes a CanExecute bool, then also demand a Predicate to handle CanExecute
    this.DoItCommand = new RelayCommand(() => this.SomePrivateMethod(maybeEvenAnEnclosedParam), aCanExecutePredicate);
  }
}
于 2014-04-22T16:21:22.307 に答える