当我想 a) 消除公共合同中的噪音但 b) 还与派生类共享功能时,我个人使用 protected 修饰符,而反过来 c) 编写 DRY 代码并且它还注意单一责任原则。听起来很典型,我敢肯定,但让我举个例子。
这里我们有一个基本的查询处理程序接口:
public interface IQueryHandler<TCommand, TEntity>
{
IEnumerable<TEntity> Execute(TCommand command);
}
此接口由应用程序中的许多不同查询处理程序实现。稍后假设我需要缓存来自许多不同查询处理程序的查询结果。不过,这实际上只是一个实现细节。一般而言,任何具体查询处理程序类的消费者都不关心这一点。然后我的解决方案是创建一个实现,该实现负责缓存,但将实际查询责任推迟到任何派生类。
public abstract class CachedQueryHandler<TCommand, TEntity>
: IQueryHandler<TCommand, TEntity>
{
public IEnumerable<TEntity> Execute(TCommand command)
{
IEnumerable<TEntity> resultSet = this.CacheManager
.GetCachedResults<TEntity>(command);
if (resultSet != null)
return resultSet;
resultSet = this.ExecuteCore(command);
this.CacheManager.SaveResultSet(command, resultSet);
return resultSet;
}
protected abstract IEnumerable<TEntity> ExecuteCore(TCommand command);
}
CachedQueryHandler 不打算让其他任何人直接调用 ExecuteCore 方法。它也不关心查询是如何实现的。protected 修饰符非常适合这样的场景。
此外,我不想在每个查询处理程序中重复相同的样板类型的代码,特别是因为如果缓存管理器接口发生更改,重构将是一场噩梦,如果在此完全删除缓存,那将是一个真正的痛苦等级。
这是一个具体的小部件查询处理程序的样子:
public class DatabaseWidgetQueryHandler : CachedQueryHandler<WidgetCommand, Widget>
{
protected override IEnumerable<Widget> ExecuteCore(WidgetCommand command)
{
return this.GetWidgetsFromDatabase();
}
}
现在,如果小部件查询处理程序的消费者通过查询处理程序接口使用它,如果我使用依赖注入,我当然可以强制执行,他们将永远不会使用添加到 CachedQueryProvider 类中的任何特定于缓存的东西。然后,我可以根据需要自由添加/删除缓存,或者完全更改缓存实现,即使只需很少的努力。
IQueryHandler<WidgetCommand, Widget> widgetQueryHandler;
var widgets = widgetQueryHandler.Execute(myWidgetCommand);