在我们的C#MVC应用程序中,我们有很多接口,它们将实现它们的对象映射为1到1。即:基本上,对于每个创建的对象,都执行了“提取接口”操作。
Moq使用接口为我们的单元测试生成模拟对象。但这是接口重复使用的唯一时间。
我们系统中没有具体对象实现多个接口。
有谁能告诉我这是否会导致问题在路上?如果是这样,他们会是什么?
我在想,我们的应用程序中存在大量重复,例如在这两个界面中(编辑:在我们的服务层中)唯一不同的是方法名称和参数类型,但是在语义上,他们对发送消息的存储库执行相同的操作:
interface ICustomer
{
void AddCustomer(Customer toAdd);
void UpdateCustomer(Customer toUpdate);
Customer GetById(int customerId);
}
interface IEmployee
{
void AddEmployee(Employee toBeAdded);
void UpdateEmployee(Employee toUpdate);
Employee GetById(int employeeId);
}
这就是我认为重用的抽象原则会出现的地方,即将代码转换为类似的东西:
public interface IEmployee: IAdd<Employee>, IUpdate<Employee>, IFinder<Employee>
这不是关于存储库模式 - 这是关于任何层中的接口,看起来它们共享语义相同的行为。是否值得为这些操作派生通用接口并使“子接口”继承它们?
至少它会保持方法的签名一致。但这会给我带来什么其他好处呢? (抛开利斯科夫替代原则)
现在,方法的名称和返回类型都到处都是。
我读过Mark Seemann关于重用抽象原理的博客,但我不明白,坦率地说。也许我只是愚蠢:)我还阅读了Fowler对Header Interfaces的定义。
答案 0 :(得分:10)
所有这些都可以使用Repository pattern
...
public interface IRepository<TEntity> where TEntity : IEntity
{
T FindById(string Id);
T Create(T t);
bool Update(T t);
bool Delete(T t);
}
public interface IEntity
{
string Id { get; set; }
}
我们系统中没有具体对象实现多个接口。
有谁能告诉我这是否会导致问题在路上? 如果是这样,他们会是什么?
是的,如果还没有开始这样做会导致问题。
您最终会得到一堆接口,这些接口不会为您的解决方案增加任何内容,从而耗费大量时间来维护和创建它们。随着代码库大小的增加,您会发现并非所有内容都像您曾经想象的那样具有凝聚力
请记住,接口只是一种工具,是实现抽象级别的工具。而抽象是一个概念,一个模式,一个由许多独立实体共享的原型。
你总结了这个,
这不是关于存储库模式 - 这是关于任何层中的接口,看起来它们共享语义相同的行为。是否值得为这些操作派生通用接口并使“子接口”继承它们?
这与interfaces
无关,约为abstractions
,Repository pattern
演示了如何抽象出针对特定对象的行为。
我上面给出的示例没有任何名为AddEmployee
或UpdateEmployee
的方法......这样的方法只是浅层接口,而不是抽象。
Repository pattern
的概念很明显,它定义了一组行为,这些行为由许多不同的类实现,每个类都为特定实体量身定制。
考虑到为每个实体(UserRepository,BlogRepository等)实现了存储库,并且考虑到每个存储库必须支持一组核心功能(基本CRUD操作),我们可以采用该核心功能集并在其中定义接口,然后在每个存储库中实现该接口。
现在我们可以从Repository模式中学到一些东西,并将它应用到我们应用程序的其他部分,从而定义一个由新接口中的多个对象共享的核心行为集,然后源自该接口。
public interface IVehicleOperator<TVehicle> where TVehicle : IVehicle
{
void Accelerate();
void Brake();
}
这样做我们不再有1:1的映射,而是实际的抽象。
虽然我们正在谈论这个话题,但也值得回顾decorator pattern
。
答案 1 :(得分:9)
鉴于此:
interface ICustomer{
void AddCustomer(Customer toAdd);
void UpdateCustomer(Customer toUpdate);
Customer GetById(int customerId);
}
interface IEmployee
{
void AddEmployee(Employee toBeAdded);
void UpdateEmployee(Employee toUpdate);
Employee GetById(int employeeId);
}
我可能会从这样重新设计开始:
interface IRepository<T>
{
void Add(T toAdd);
void Update(T toUpdate);
T GetById(int id);
}
但是,这可能仍然违反了Interface Segregation Principle,更不用说因为它也违反了Command-Query Responsibility Segregation 模式(不是架构),它也可以&# 39;既不是合作也不是逆变。
因此,我的下一步可能是将它们分成Role Interfaces:
interface IAdder<T>
{
void Add(T toAdd);
}
interface IUpdater<T>
{
void Update(T toAdd);
}
interface IReader<T>
{
T GetById(int id);
}
此外,您可能会注意到IAdder<T>
和IUpdater<T>
在结构上完全相同(它们仅在语义上不同),所以为什么不将它们设为一个:
interface ICommand<T>
{
void Execute(T item);
}
为了保持一致,您也可以重命名IReader<T>
:
interface IQuery<T>
{
T GetById(int id);
}
基本上,你可以将所有减少到这两个界面,但对于某些人来说,这可能过于抽象,并且携带的语义信息太少。
但是,我不认为可以提供更好的答案,因为前提是有缺陷的。最初的问题是如何设计界面,但客户端无处可见。如APPP ch。 11教我们,&#34;客户端[...]拥有抽象接口&#34; - 换句话说,客户端根据需要定义接口。接口不应该从具体类中提取出来。
关于这个主题的进一步研究材料: