jay*_*jay 2 c# abstraction design-patterns dependency-injection
假设我想出于不同的原因抽象集合上的操作:
现在为了简单起见,让我们推荐一个集合
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; }
}
Run Code Online (Sandbox Code Playgroud)
我有一个类型只需要搜索Book::Title(区分大小写),所以我可以定义我的抽象:
interface ITitleSearcher {
bool ContainsTitle(string title);
}
Run Code Online (Sandbox Code Playgroud)
然后实施
class CaseSensitiveTitleSearcher : ITitleSearcher { ... }
class NoCaseSensitiveTitleSearcher : ITitleSearcher { ... }
Run Code Online (Sandbox Code Playgroud)
并将其消费为
class TitleSearcherConsumer {
public TitleSearcherConsumer(ITitleSearcher searcher) { // <- ctor injection
}
}
Run Code Online (Sandbox Code Playgroud)
在此之前,对我来说一切都很清楚,而且我理解的还有接口隔离原则.
继续开发我必须满足其他需求,所以我定义然后实现其他接口,ITitleSearcher例如:
class CaseSensitiveSubTitleSearcher : ISubTitleSearcher { ... }
class SoldWithDateRangeSearcher : ISoldDateRangeSearcher { ... }
Run Code Online (Sandbox Code Playgroud)
为了不违反DRY(不要重复自己),我可以创建一个包装器IEnumerable<Book>:
class BookCollection : ITitleSearcher, ISubTitleSearcher, ISoldDateRangeSearcher
{
private readonly IEnumerable<Book> books;
public BookCollection(IEnumerable<Book> books)
{
this.books = books;
}
//...
}
Run Code Online (Sandbox Code Playgroud)
现在,如果我有一个消费者,TitleSearcherConsumer我可以毫无问题地传递一个实例BookCollection.
但如果我是这样的消费者:
class TitleAndSoldSearcherConsumer {
public TitleAndSoldSearcherConsumer(ITitleSearcher src1, ISoldDateRangeSearcher src2) {
}
}
Run Code Online (Sandbox Code Playgroud)
我无法将一个BookCollection实例注入TitleAndSoldSearcherConsumerctor; 我要传递每个接口的实现.
是的,我可以IBookCollection使用其他接口的所有方法定义并在所有消费者中使用它,但这样做并不违反ISP?
我可以同时接近ISP/SOLID和DRY吗?
是的,我可以使用其他接口的所有方法定义IBookCollection并在所有消费者中使用它,但这样做不会违反ISP吗?
您不会违反ISP,但您的图书集将开始承担太多责任,您将违反单一责任原则.
让我担心的另一件事是ITitleSearcher接口的多个实现.我不确定这里是否存在违反某些设计原则的情况,但您的设计中似乎存在一些您应该注意的模糊性.此外,对于每个搜索操作,您都在创建一个新的抽象.你已经拥有了ITitleSearcher,ISubTitleSearcher而且ISoldDateRangeSearcher可能会增加几十个.我认为你在这里缺少的是对系统中查询的一般抽象.所以这是你可以做的:
定义查询参数的抽象:
public interface IQuery<TResult> { }
Run Code Online (Sandbox Code Playgroud)
这是一个没有成员的接口,只有一个泛型类型TResult.在TResult描述了查询的返回类型.例如,您可以按如下方式定义查询:
public class SearchBooksByTitleCaseInsensitiveQuery : IQuery<Book[]>
{
public string Title;
}
Run Code Online (Sandbox Code Playgroud)
这是接受Title和返回的查询的定义Book[].
您还需要的是对知道如何处理特定查询的类的抽象:
public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
Run Code Online (Sandbox Code Playgroud)
看看该方法如何处理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();
}
}
Run Code Online (Sandbox Code Playgroud)
现在消费者可以依赖于这样的具体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"
});
}
}
Run Code Online (Sandbox Code Playgroud)
这究竟给我带来了什么?
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个装饰器,可以在运行时包装所有命令处理程序实现,增加了验证它的能力.
有关更多背景信息,请查看本文:同时...在我的架构的查询方面.