c# - 装饰多个实现的接口之一
问题描述
我想知道是否可以在 C# 中为多个已实现接口中的 1 个提供装饰器。我倾向于不,但也许。
这就是我的意思
public abstract class Auditable
{
public string CreatedBy { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
public string ModifiedBy { get; set; }
}
public class MyClass : Auditable
{
// <...> properties
}
public interface IWriteRepository<T> : where T : Auditable
{
T Create(T entity);
T Update(T entity);
}
public class AuditRepositoryDecorator<T> : IWriteRepository<T> where T : Auditable
{
private readonly IWriteRepository<T> _decorated;
// <...> ctor with injects
public T Create(T entity)
{
entity.ModifiedAt = time;
entity.CreatedAt = time;
entity.CreatedBy = invoker;
entity.ModifiedBy = invoker;
return _decorated.Create(entity);
}
public T Update(T entity)
{
entity.ModifiedAt = time;
entity.ModifiedBy = invoker;
return _decorated.Update(entity);
}
}
public interface IMyClassRepository : IWriteRepository<MyClass>
{
MyClass Get(int id);
}
所以我希望能够依赖IMyClassRepository
存储库,并且无论何时Create
或Update
将被调用它都会通过AuditRepositoryDecorator
. 这是一段被执行很多次的逻辑,我认为作为装饰器而不是与某些执行相同操作的接口建立组合关系会简单得多。
IAuditableRepository
永远不会直接实例化,因为它总是由另一个接口实现,所以我认为可能无法做到我想要实现的目标。
我正在使用带有Scrutor的默认 dnc2.1 DI 框架进行装饰。
解决方案
你想要达到的目标是不可能的。这不是使用的 DI 容器的限制,而是 .NET 类型系统的限制。我经常建议遇到 DI 麻烦的开发人员,为了便于理解,从等式中删除 DI 容器,而是手动构建对象图。正如我将在下面演示的那样,这在您的情况下效果很好。
假设您有一个IMyClassRepository
消费者:
public class RepoConsumer
{
RepoConsumer(IMyClassRepository repo) ...
}
和一个IMyClassRepository
实现:
public class MyClassRepositoryImpl : IMyClassRepository
{
...
}
现在让我们为RepoConsumer
它创建对象图AuditRepositoryDecorator<MyClass>
:
var repo = new MyClassRepositoryImpl();
var decoratedRepo = new AuditRepositoryDecorator<MyClass>(repo);
var consumer = new RepoConsumer(decoratedRepo); // <-- COMPILE ERROR
When you compile this code, you'll notice that the C# compiler will generate an error on the new RepoConsumer
line. This is because RepoConsumer
expects an IMyClassRepository
. Although MyClassRepositoryImpl
implements IMyClassRepository
, AuditRepositoryDecorator<MyClass>
does not implement IMyClassRepository
.
To solve this, you might try letting AuditRepositoryDecorator<T>
implement IMyClassRepository
, but that will obviously be ugly, because the decorator will have to implement a dozen of interfaces, for each entity in your system.
But what this exercise proves, is that the problem is not so much with the DI Container, but rather that the type system simply not permits you to build an object graph of this. And since the type system doesn't allow you to, the DI Container certainly won't allow it. It can't work around the type checks of the type system. Fortunately.
But the solution to your problem is actually really straightforward: remove the specific IMyClassRepository
and let consumers depend on IWriteRepository<MyClass>
instead. This might seem a disappointing solution, but there is a myriad of problems surrounding deriving from generic interfaces. Just accept the fact that consumers depend on such generic abstraction. It takes some time, but eventually, you will start to love and appreciate this style of programming.
But, of course, this still leaves us with the question of how to add new methods, such as MyClass Get(string)
. There are multiple solutions, such as:
- Implement it as extension method (only possible when the method itself requires access to the interface itself, not to the class's internals)
- Define a separate interface, which might be a good idea in general, according to the Interface Segregation Principle
推荐阅读
- javascript - Slick Slider 首先显示最后一个元素
- php - SQL ORDER results BY multiple clauses
- javascript - jquery IF 不做某事 ELSE 什么都不做 - 不工作
- php - 尝试访问类型为 null sql 错误的值的数组偏移量
- java - Spring Boot jar 忽略 application.properties
- c# - 如何确保条目具有不重叠的时间范围?
- postgresql - Symfony 5 Doctrine Postgres 通过 SSL 连接
- c++ - c++解构函数返回值
- python - 如何解决 ModuleNotFoundError: No module named 'flask_mysqldb' 在 Vs.Code 中的错误?
- python-2.7 - python中的分位数回归