2

さまざまな理由で、コレクションに対する操作を抽象化するとします。

簡単にするために、コレクションの理由を説明しましょう

class Book {
  public string Title { get; set; };
  public string SubTitle { get; set; }
  public bool IsSold { get; set; }
  public DateTime SoldDate { get; set; }
  public int Volums { get; set; }
}

私はBook::Title(大文字と小文字を区別するかどうかにかかわらず)のみを検索する必要があるタイプを持っているので、抽象化を定義できます:

interface ITitleSearcher {
  bool ContainsTitle(string title);
}

そして実装する

class CaseSensitiveTitleSearcher : ITitleSearcher { ... }
class NoCaseSensitiveTitleSearcher : ITitleSearcher { ... }

そしてそれを消費する

class TitleSearcherConsumer  {
  public TitleSearcherConsumer(ITitleSearcher searcher) { // <- ctor injection
  }
}

ここまではすべてが明確であり、私が理解していることについては、インターフェイス分離の原則も遵守されています。

開発を続けると、他の要件を満たす必要があるため、次のような他のインターフェイスを定義して実装しますITitleSearcher

class CaseSensitiveSubTitleSearcher : ISubTitleSearcher { ... }
class SoldWithDateRangeSearcher : ISoldDateRangeSearcher { ... }

DRYに違反しないように(繰り返さないでください)、ラッパーを作成できますIEnumerable<Book>

class BookCollection : ITitleSearcher, ISubTitleSearcher, ISoldDateRangeSearcher
{
  private readonly IEnumerable<Book> books;

  public BookCollection(IEnumerable<Book> books)
  {
    this.books = books;
  }
  //...
}

TitleSearcherConsumerのインスタンスを問題なく渡すことができるような消費者がいる場合BookCollection

しかし、私がこのような消費者を持っている場合:

class TitleAndSoldSearcherConsumer {
  public TitleAndSoldSearcherConsumer(ITitleSearcher src1, ISoldDateRangeSearcher src2) {

  }
}

BookCollectionインスタンスをTitleAndSoldSearcherConsumerctorに注入できません。各インターフェースの実装を渡す必要があります。

はい、他のインターフェイスのすべてのメソッドで を定義しIBookCollectionて、すべてのコンシューマーで使用できますが、そうしても ISP に違反しませんか?

ISP/SOLID と DRY を同時に近くに置くことはできますか?

4

2 に答える 2

8

はい、他のインターフェイスのすべてのメソッドを使用して IBookCollection を定義し、すべてのコンシューマーで使用できますが、そうしても ISP に違反しませんか?

ISP に違反することはありませんが、書籍コレクションがあまりにも多くの責任を負うようになり、単一責任の原則に違反することになります。

私が心配しているもう 1 つのことは、ITitleSearcherインターフェイスの複数の実装です。ここで何らかの設計原則に違反しているかどうかはわかりませんが、おそらくあなたが見なければならない設計のあいまいさがあるようです. さらに、検索操作ごとに、新しい抽象化を作成しています。ITitleSearcherISubTitleSearcher、およびがすでにあり、ISoldDateRangeSearcherさらに数十個追加される可能性があります。ここで見逃していると思うのは、システム内のクエリに対する一般的な抽象化です。したがって、ここでできることは次のとおりです。

クエリ パラメータの抽象化を定義します。

public interface IQuery<TResult> { }

これは、単一のジェネリック型を持つメンバーのないインターフェイスTResultです。はTResult、そのクエリの戻り値の型を示しています。たとえば、次のようにクエリを定義できます。

public class SearchBooksByTitleCaseInsensitiveQuery : IQuery<Book[]>
{
    public string Title;
}

これは、 を受け取ってTitleを返すクエリの定義ですBook[]

また、特定のクエリを処理する方法を知っているクラスの抽象化も必要になります。

public interface IQueryHandler<TQuery, TResult>
    where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

メソッドがどのように を受け取り、 ?TQueryを返すかを確認してください。TResult実装は次のようになります。

public class SearchBooksByTitleCaseInsensitiveQueryHandler :
    IQueryHandler<SearchBooksByTitleCaseInsensitiveQuery, Book[]>
{
    private readonly IRepository<Book> bookRepository;

    public SearchBooksByTitleCaseInsensitiveQueryHandler(
        IRepository<Book> bookRepository) {
        this.bookRepository = bookRepository;
    }

    public Book[] Handle(SearchBooksByTitleCaseInsensitiveQuery query) {
        return (
            from book in this.bookRepository.GetAll()
            where book.Title.StartsWith(query.Title)
            select book)
            .ToArray();
     }
}

IQueryHandler<TQuery, TResult>現在、消費者は次のような特定の実装に依存できます。

class TitleSearcherConsumer  {
    IQueryHandler<SearchBooksByTitleCaseInsensitiveQuery, Book[]> query;
    public TitleSearcherConsumer(
      IQueryHandler<SearchBooksByTitleCaseInsensitiveQuery, Book[]> query) {
    }

    public void SomeOperation() {
        this.query.Handle(new SearchBooksByTitleCaseInsensitiveQuery
        {
            Title = "Dependency Injection in .NET"
        });
    }
}

そして、これは私に何をもたらしますか?

  • クエリを定義することIQueryHandler<TQuery, TResult>で、システム内の非常に一般的なパターン (クエリ) に対する一般的な抽象化を定義しました。
  • IQueryHandler<TQuery, TResult>単一のメンバーを定義し、ISP に準拠します。
  • IQueryHandler<TQuery, TResult>実装は単一のクエリを実装し、SRP に準拠します。
  • このIQuery<TResult>インターフェースにより、クエリとその結果に対するコンパイル時のサポートが可能になります。コンシューマーは、コンパイルされないため、戻り値の型が正しくないハンドラーに誤って依存することはできません。
  • 共通のIQueryHandler<TQuery, TResult>抽象化により、実装を変更することなく、あらゆる種類の分野横断的な問題をクエリ ハンドラーに適用できます。

特にこの最後の点は重要です。検証、承認、ロギング、監査追跡、監視、キャッシングなどの分野横断的な問題はすべて、ハンドラーの実装とコンシューマーの両方を変更する必要なく、デコレーターを使用して非常に簡単に実装できます。これを見てください:

public class ValidationQueryHandlerDecorator<TQuery, TResult>
    : IQueryHandler<TQuery, TResult>
    where TQuery : IQuery<TResult>
{
    private readonly IServiceProvider provider;
    private readonly IQueryHandler<TQuery, TResult> decorated;

    public ValidationQueryHandlerDecorator(
        Container container,
        IQueryHandler<TQuery, TResult> decorated)
    {
        this.provider = container;
        this.decorated = decorated;
    }

    public TResult Handle(TQuery query)
    {
        var validationContext =
            new ValidationContext(query, this.provider, null);

        Validator.ValidateObject(query, validationContext);

        return this.decorated.Handle(query);
    }
}

これは、実行時にすべてのコマンド ハンドラーの実装をラップして、検証する機能を追加できるデコレータです。

詳細な背景情報については、次の記事を参照してください: While... on the query side of my architecture .

于 2013-03-14T13:25:07.647 に答える
1

あなたのインターフェースはあまりにも具体的です。述語を持つ

interface ISearcher {
  bool IsAMatch(Book book);
}

そこから検索者を導き出します。さらに、コレクションに検索機能を入れないでください。コレクションは保存と反復のためのものです。訪問者パターンについて説明しただけかもしれません。

于 2013-03-14T12:40:34.077 に答える