【问题标题】:Resolve forward references in modular architecture解决模块化架构中的前向引用
【发布时间】:2015-12-16 17:17:36
【问题描述】:

在我的 (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的所有类都依赖于当前类;所以这些是我要解决的循环引用。

有没有解决这种“复杂事件”机制的现有模式?或者有没有其他更好的方法来解决这个问题?

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

【问题讨论】:

    标签: c# architecture module


    【解决方案1】:

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

    我建议您逐步应用dependency inversion principleinterface segregation principle。通过构造函数将新引入的接口传递给客户端,并使用 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 
    

    【讨论】:

    • 抱歉,我无法理解这两个原则将如何解决我的订购问题。你能举个例子吗?
    • @SebastianKrysmanski 他们没有。我的建议是根本不使用事件来解决循环依赖问题。你仍然需要顺序调用正确的依赖,但是循环依赖没有了。
    • 据我了解您的问题,这是您要解决的根本问题。
    【解决方案2】:

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

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

    这是一种脆弱的方法。让我们假设依赖项是这样命名的:

    • 消息管理器
    • 项目经理
    • 用户管理器

    动机是消息管理器必须首先运行,以便它可以找到用户管理器中的所有用户和项目管理器中的所有项目才能删除它的消息。

    您可以反转该过程。监听事件UserDeleted 删除用户的所有消息,监听ProjectDeleted 甚至删除项目的所有消息。

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

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

    创建一个名为IEventHandler&lt;T&gt;的新接口:

    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);
            }
        }
    }
    

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 2011-09-04
      • 1970-01-01
      • 1970-01-01
      • 2016-04-26
      • 2011-04-23
      • 1970-01-01
      • 1970-01-01
      • 2014-10-27
      • 1970-01-01
      相关资源
      最近更新 更多