【问题标题】:Decorating 1 of multiple implemented interfaces装饰多个实现的接口之一
【发布时间】:2020-11-02 09:44:52
【问题描述】:

我想知道是否可以在 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 存储库,并且每当CreateUpdate 被调用时,它都会通过AuditRepositoryDecorator。这是一段经常执行的逻辑,我认为作为装饰器而不是与某些执行相同操作的接口建立组合关系会简单得多。

IAuditableRepository 永远不会直接实例化,因为它总是由另一个接口实现,所以我认为可能无法实现我想要实现的目标。

我使用带有Scrutor 的默认dnc2.1 DI 框架进行装饰。

【问题讨论】:

    标签: c# design-patterns dependency-injection decorator


    【解决方案1】:

    你想要达到的目标是不可能的。这不是使用的 DI 容器的限制,而是 .NET 类型系统的限制。我经常建议陷入 DI 麻烦的开发人员,为了便于理解,从等式中删除 DI 容器,而是手动构建对象图。正如我将在下面演示的那样,这在您的情况下效果很好。

    假设您有一个IMyClassRepository 消费者:

    public class RepoConsumer
    {
        RepoConsumer(IMyClassRepository repo) ...
    }
    

    还有一个IMyClassRepository 实现:

    public class MyClassRepositoryImpl : IMyClassRepository
    {
        ...
    }
    

    现在让我们为使用AuditRepositoryDecorator&lt;MyClass&gt;RepoConsumer 创建对象图:

    var repo = new MyClassRepositoryImpl();
    var decoratedRepo = new AuditRepositoryDecorator<MyClass>(repo);
    var consumer = new RepoConsumer(decoratedRepo); // <-- COMPILE ERROR
    

    当您编译此代码时,您会注意到 C# 编译器将在 new RepoConsumer 行生成错误。这是因为 RepoConsumer 需要 IMyClassRepository。虽然MyClassRepositoryImpl 实现了IMyClassRepository,但AuditRepositoryDecorator&lt;MyClass&gt; 没有 实现IMyClassRepository

    要解决这个问题,您可以尝试让 AuditRepositoryDecorator&lt;T&gt; 实现 IMyClassRepository,但这显然很难看,因为装饰器必须为系统中的每个实体实现十几个接口。

    但是这个练习证明,问题不在于 DI 容器,而在于类型系统根本不允许您构建它的对象图。而且由于类型系统不允许您这样做,DI Container 肯定不会允许它。它无法绕过类型系统的类型检查。还好。

    但您的问题的解决方案实际上真的很简单:删除特定的IMyClassRepository,让消费者依赖IWriteRepository&lt;MyClass&gt;。这似乎是一个令人失望的解决方案,但是从泛型接口派生存在无数问题。只需接受消费者依赖于这种通用抽象的事实。这需要一些时间,但最终,您会开始喜欢并欣赏这种编程风格。

    但是,当然,这仍然给我们留下了如何添加新方法的问题,例如MyClass Get(string)。有多种解决方案,例如:

    • 将其实现为扩展方法(仅当方法本身需要访问接口本身而不是类的内部时才可能)
    • 根据Interface Segregation Principle定义一个单独的接口,一般来说这可能是个好主意

    【讨论】:

      【解决方案2】:

      在这些情况下,最常用的方法是存储库模式,如我的回答中所述:How do I avoid code repetition when defining a class of functions that only vary the data type of the parameters they handle?

      在您的情况下,这是类层次结构:

      public interface IWriteRepository<T> : where T : Auditable
      {
         T Create(T entity);
         T Update(T entity);
      }
      
      public abstract class WriteRepositoryBase<T> : IWriteRepository<T> where T : Auditable
      {
          //implement create and update
      }
      public interface IMyRepository : IWriteRepository<MyClass>
      {
           MyClass Get(string id);
      }
      
      public class MyRepository : WriteRepositoryBase<MyClass>, IMyRepository
      {
           //implement Get
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-07-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-01
        • 2017-07-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多