【问题标题】:Simplifying composition interfaces in C#在 C# 中简化组合接口
【发布时间】:2017-02-14 15:36:40
【问题描述】:

在我正在处理的代码中,我有一个结构,其中代码的某些部分取决于当前的软件会话。软件会话包含多个辅助对象,它们是通过组合注入的依赖项。

一个例子是注入到它的IRepository,它包含对数据存储库的访问。 IRepository 包含一个 DatabaseContext,它再次通过注入的 IDbContext 写入数据库。

SoftwareSession 是唯一注入的通用基础架构,用于一直访问数据库,充当网关。这意味着当我想将一个对象写入数据库时​​,例如 WriteCar,我将必须实现 3 个接口、2 个委托给组合对象的函数和 1 个带有实现的函数。它在下面的代码片段中得到了澄清。 WriteCar 签名在 3 个接口(IRepository、ISoftwareSession、IDbContext)中定义相同,2 个未实现的地方(Repository、SoftwareSession)仅调用复合对象相关函数和1 个实际实现的地方(IDbContext)

这意味着当我想要重构、移动代码、添加功能或更改函数签名时,我总是需要为一个函数更改 6 个位置。

我认为这为提高可测试性提供了最佳环境,并且它遵循最佳实践,其中软件会话封装了对存储库的访问,而存储库封装了对数据上下文的访问——但我仍然在质疑我们是否可以有更好的方法来编写一次,还是我对下面代码中的某些概念有误解?

在架构上更易于维护的实现方式是什么?甚至可能使用一些巧妙的 lambda 或委托方式来减少为每个新功能编写的代码量?甚至是一些库(如 automapper 简化了 DTO)或工具来简化使用 Visual Studio、Resharper 等从某种模板机制生成此代码?

如果我在这里有一些概念混淆,请告诉我。我知道我的一些同事也有类似的观点,在这种情况下,澄清其他人的误解可能会有所帮助。

public class SoftwareSession : ISoftwareSession
{
    ...
    IRepository repository;
    public void WriteCar(Car car){
        repository.WriteCar(car);
    }
    ...
}

public interface ISoftwareSession{
    ...
    void WriteCar(Car car);
    ...
}


public class Repository : IRepository{
    ...
    IDbContext context;
    public void WriteCar(Car car){
        context.WriteCar(car);
    }
    ...        
}

public interface IRepository{
    ...
    void WriteCar(Car car);
    ...
}

public class MyDbContext : IDbContext{
    ...
    public void WriteCar(Car car){
       //The Actual Implementation here.
        ...
    }
    ...
}

public interface IDbContext{
    ...
    void WriteCar(Car car);
    ...
}

【问题讨论】:

  • 接口也可以继承!为什么没有ICarWriter 接口,然后是IRepository : ICarWriterISoftwareSession : ICarWriter 等等。
  • 我相信问题出在您的软件会话实现中,这违反了单一职责,因为它出现在您的框架中,任何进入数据库、writeCar、writeCustomer 等的东西......必须有一个访问者类。
  • @BarryO'Kane 我同意 - 这是一种重构接口的方式 - 从减少重复代码的角度来看是有意义的。不过,我愿意等待其他可能的简化方法。
  • @Dys1 我也同意你的观点,但这不是我们在使用层时使用的模式(组合优于继承)吗?数据库层与视图模型分离,视图模型使用软件会话作为单独的层/代理,而不是直接访问数据库上下文。如果我们决定更改我们用于 dbcontext 的库,例如EF 到 Ado.net 的一些签名更改,我们只在软件会话中更改它。然而,大多数情况并非如此,我很好奇是否有一种方法可以将其实现为可定制的配置约定

标签: c# oop design-patterns dependency-injection delegation


【解决方案1】:

一方面,您的IDbContextIRepository 是相同的。您可能想删除IDbContext,或者至少删除在IRepository 中声明的方法。

然后,MyDbContextRepository 都将实现 IRepositoryRepository 类将只是 MyDbContext 的包装器。

那么,如果Repository 只是将呼叫转发到MyDbContext,那么您可能也不需要该类。

此外,除了将调用转发到包含的存储库之外,我没有看到您在 SoftwareSession 中执行任何操作。您真的需要SoftwareSession,还是将IRepository 直接传递给调用会话对象的人是否有意义?

底线是这个实现充满了复制和转发。删除它,您的整个模型就会变得简单。

【讨论】:

  • 谢谢卓然。添加 IRepository 是因为有多个 DbContext(例如,一个上下文可能是您正在查看路标的 DrivingContext,而另一个上下文可能是我们正在实现与维护相关的功能的维护或管理上下文)。在这种情况下,存储库在我的实现中充当不同上下文的聚合器。如果我不使用软件会话,我将不得不将我的存储库或 DbContexts 注入到调用这些函数的每个其他类中,这不会导致额外的复制并破坏封装吗?
【解决方案2】:

没有看到您的组合根,我不完全确定您的实现是如何工作的,但我建议研究使用控制反转 (IoC) 容器。由于您的 ISoftwareSession 实现仅依赖于 IRepository 实例,因此您只需将其注入到类的构造函数中。 IRepository 实现也是如此:您只需将 IDbContext 注入构造函数即可。

使用 IoC 容器,您可以“注册”,即在应用程序启动时(在组合根中)将您的接口连接到您的实现,当您解决依赖关系时,容器会负责创建所需的实例。然后,您所要做的就是从容器中获取SoftwareSession 的实例,然后就可以离开了。

因此,您可以像这样更改您的 SoftwareSession 实现:

public class SoftwareSession : ISoftwareSession
{
    IRepository repository;

    public SoftwareSession(IRepository repository)
    {
        this.repository = repository;
    }

    public void WriteCar(Car car)
    {
        repository.WriteCar(car);
    }
}

你的Repository 实现是这样的:

public class Repository : IRepository
{
    IDbContext context;

    public Repository(IDbContext dbContext)
    {
        context = dbContext;
    }

    public void WriteCar(Car car)
    {
        context.WriteCar(car);
    }
}

那么这里是你的作文根:

var ioc = new MyIocContainer();

// register your interfaces and their associated implementation types with the IoC container
ioc.Register<ISoftwareSession, SoftwareSession>();
ioc.Register<IRepository, Repository>();
ioc.Register<IDbContext, MyDbContext>();

// resolve the IoC container
ioc.Resolve();

// get your `ISoftwareSession` instance
var session = ioc.GetConcrete<ISoftwareSession>();

var newCar = new Car();

session.WriteCar(newCar);

【讨论】:

  • 罗里感谢您的回答。我已经为问题中的每个项目使用了带有构造函数注入的 Ninject。问题不在于实际的注入,而在于使用此架构在每个函数的 6 个位置使用 WriteCar 接口和实现委托。我所有剩余的主线类只有通过构造函数注入的 SoftwareSession 并且它们调用例如该软件会话上要调用的 WriteCar 函数。然而,我必须编写/复制签名 6 次才能真正实现。
  • 我不明白您所说的“每个函数在 6 个地方使用 WriteCar 接口和实现委托”是什么意思。我没有看到WriteCar 接口。什么是“实施委托”?您所说的函数(方法?)中的“六个地方”在哪里?我在你提供的代码中没有看到,所以我很困惑......
  • 很抱歉没有澄清这个 Rory,我也会在问题中更正它。 WriteCar 签名在 3 个接口(IRepository、ISoftwareSession、IDbContext)中定义相同,2 个未实现的地方(Repository、SoftwareSession)仅调用复合对象相关函数和 1 个实际实现的地方(IDbContext)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-06
  • 1970-01-01
  • 1970-01-01
  • 2015-07-21
  • 2014-06-01
  • 1970-01-01
相关资源
最近更新 更多