解决模块化体系结构中的前向引用

本文关键字:引用 模块化 体系结构 解决 | 更新日期: 2023-09-27 18:26:13

在我的(C#)软件项目中,我有几个模块(由单独的Visual Studio项目表示),由于历史原因,它们有很多循环依赖关系(紧密耦合)。

我目前正在尝试解析/删除循环引用。我已经添加了依赖项注入,但这当然不能解决/删除循环引用。

因此,我考虑使用事件来解决"正向"依赖关系。它们不直接调用前向依赖项,而是在该事件上注册。

然而,这些依赖关系中的一些需要在其他依赖关系之前执行,例如:

void DeleteCompany(int companyId)
{
    // must run before other dependencies
    ForwardDepend1.OnCompanyDeleted(companyId);
    // can run in parallel
    ForwardDepend2.OnCompanyDeleted(companyId);
    ForwardDepend3.OnCompanyDeleted(companyId);
    // must run as last command
    this.OnCompanyDeleted(companyId);
}

ForwardDependX的所有类都依赖于当前类;所以这些是我想要解决的循环引用。

是否有任何现有的模式可以解决这种"复杂事件"机制?或者还有其他更好的方法来解决这个问题吗?

我试过用谷歌搜索这个问题,但找不到任何好的搜索关键字。

解决模块化体系结构中的前向引用

我认为使用事件来改进紧密耦合的设计不是最好的选择,因为它们往往会使依赖关系不那么明显。而且,您确实需要显式地处理所有依赖项来处理循环引用。

我建议您逐步应用依赖反转原则和界面分离原则。通过构造函数将新引入的接口传递给它们的客户端,并使用DI来组合它们

此外,依赖关系可视化工具在这些情况下确实很有帮助。我过去曾使用Resharper和NDepend来完成这些任务,两者都运行良好。

DIP示例

下面是一个示例,展示了如何使用DIP解决循环依赖关系。因此,假设您有两个相互依赖的类:

class A 
{
  private readonly B _b;
  public A(B b) {
      _b = b;
  }
  public void DoIt() {
    // ...
  }
}
class B 
{
  public void DoSomething(A a) {
    a.DoIt();
  }
}

这意味着我们有一个像这样的依赖关系图:

A <--> B

现在在类上引入一个接口,该接口构成较弱的依赖关系,在本例中为A(因为A仅在B中临时使用)。

interface IA 
{
  void DoIt();
}
class A : IA
{
  private readonly B _b;
  public A(B b) {
      _b = b;
  }
  public void DoIt() {
    // ...
  }
}
class B 
{
  public void DoSomething(IA a) {
    a.DoIt();
  }
}

随着接口的引入,依赖循环已经被打破:

 A ----
 |     |
 v     v
IA <-- B 

这不会解决你的问题。相反,您应该创建多个事件并将它们连接起来。

您说您有依赖项1-3,必须在CompanyDeleted事件上按顺序运行,并且您自己的事件处理程序应该最后运行。

这是一种脆弱的做法。让我们假设依赖项的名称如下:

  • 消息管理器
  • 项目经理
  • UserManager

其动机是消息管理器必须首先运行,这样它才能从用户管理器中找到所有用户,从项目管理器中查找所有项目,从而能够删除其消息。

相反,你可以颠倒这个过程。监听事件UserDeleted以删除用户的所有消息,甚至监听ProjectDeleted以删除项目的所有消息。

所以现在CompanyManager的事件处理程序之间没有时间耦合,因为它只有一个事件处理程序

这当然意味着更多的工作,因为你必须识别新的事件并创建它们。好的方面是,您可以在IoC容器的帮助下解决事件订阅。

创建一个名为IEventHandler<T>:的新接口

public interface IEventHandler<T>
{
    void Handle(T evt);
}

并让您的类实现它:

public class UserManager : IEventHandler<CustomerDeleted>
{
    public UserManager(IUserRepository repos, IEventPublisher publisher)
    {
    }
    public void Handle(CustomerDeleted evt)
    {
        var users = _repos.FindUsersForCustomer(evt.CustomerId);
        foreach (var user in users)
        {
            _repos.Delete(user);
            _publisher.Publish(new UserDeleted(user.Id, user.CustomerId));
        }
    }
}

事件发布者本身使用您的容器(用于服务位置)。实现方式因容器而异:

public class EventPublisher : IEventPublisher
{
    public EventPublisher(IYourContainerInterface container)
    {
    }
    public void Publish<TEvent>(TEvent evt)
    {
        var handlers = _container.ResolveAll<TEvent>();
        foreach (var handler in handlers)
        {
            handler.Publish(evt);
        }
    }
}

希望能有所帮助。