设计:Class 循环依赖?

Design: Class circular dependency?

我一直在这里阅读关于解决软件设计中的循环依赖的不同答案,但我仍然无法理解。请帮我理解一下。

假设我有 class A 和 class B,它们互相调用方法:

class A {
    public:
        void Do1() {
            B b;
            b.Do1();
        }

        void Do2() {
            //do something
        }
};

class B {
    public:
        void Do1() {
            //do something
        }

        void Do2() {
            A a;
            a.Do2();
        }
};

类按照单一责任原则.

完美划分

看来我不能在这里应用依赖倒置原则,因为我可以聚合只有一个class到另一个.

根据最佳设计实践,你能建议我如何处理这个问题的正确方法吗?

非常感谢您提供一些代码的答案。

让我们从维基百科

给出的 overview of circular dependency 开始

Circular dependencies are natural in many domain models where certain objects of the same domain depend on each other. However, in software design circular dependencies between larger software modules are considered an anti-pattern because of their negative effects...

让我们打破它:

  1. Circular dependencies are natural in many domain models where certain objects of the same domain depend on each other.

上面写着自然。这是来自 Ado.Net 的示例。 DataTableDataRow 个对象

DataTable t = new DataTable("MyNewTable");
DataRow r = t.NewRow();
t.Rows.Add(r);
// Lets inspect it
Debug.WriteLine(r.Table.TableName);
// This will print - MyNewTable

在此示例中,您有一个子系统,正如他们所说,CD 是自然的。例如,您知道汽车的每个主要部件上都有 VIN 号码。让我们用伪代码实现它

class Car{

    string Vin { get; set; }
    void AddDoor(Door.Position pos){
        Door d = new Door();
        d.DoorPosition = pos;
        d.Car = this; // notice this - door knows about a car!!
        _doors.Add(d);
    }

}
class Door{
    Car CarAttachedTo;
    Position DoorPosition;

    public enum Position {
        DriverFront
        DriverRear
        PassangerFront
        PassangerRear
   } 
}

让我们想象一下,有人把你的门拉下来,警察找回了它,你需要确定这是你的门还是别人的 - 然后你做:

if (myDoor.CarAttachedTo.Vin == "MY_CAR_VIN_NUMBER")
    MessageBox.Show("The door is stolen off my car"); 

思路:在密切合作、组织在一起的子系统中,CD有时是可以的

  1. circular dependencies between larger software modules are considered an anti-pattern

您的应用程序可以在物理上位于同一个程序集(dll、exe)中,而在逻辑上它可以具有单独的逻辑层。例如,您可以将 UI、BLL 和 IO 写在同一个程序集中。因此,很容易在您的 UI 和 BLL 之间创建循环依赖关系。如果每一层都在一个单独的组件中,那么做起来就会困难得多。

CD 经常与 紧耦合 相混淆。虽然在上面的示例中我们看到了 CD 有时是多么有用,但紧耦合通常意味着一个具体对象知道另一个具体对象并且没有可重用性、可扩展性和可测试性的空间。让我们回到车上。让我们详细了解一下车上挂门的细节——你的车经过装配线,来到车门安装机

class DoorHandler{

    private Car _car;
    private MitsubishiDoorAlignmentMachine _doorMachine;        

    void HangDoors(){
        foreach (Door d in _car.Doors){
            // hang the door using 
            _doorMachine.Hang(d)
        }
    }

}    

但现在我们遇到了一个问题 - 我们的门处理程序仅适用于 MitsubishiDoorAlignmentMachine。如果我想用 FujitomoHeavyIndustriesDoorAlignmentMachine 替换它怎么办?然后我需要做这样的事情:

class DoorHandler{

    private Car _car;
    private IDoorAlignmentMachine _doorMachine;        

    void HangDoors(){
        foreach (Door d in _car.Doors){
            // hang the door using 
            _doorMachine.Hang(d)
        }
    }

}

// And when I crate my DoorHandler - I tell it which machine to use
new DoorHandler(new FujitomoHeavyIndustriesDoorAlignmentMachine());

这样我的DoorHandler就不依赖于任何特定的机器

现在,让我们回到您的情况 - AB。可能有多个答案。如果您正在创建子系统,其中您需要在对象之间进行交互但可能有多个实现 - 只需使用接口。这样你的 AB 就会知道签名而不是具体的对象。如果只有一个实现并且对象是同一程序集中同一子系统的一部分 - 相互了解可能就可以了。

您需要通过将它们放入另一个 class 来移除循环依赖。从高层次上看,循环依赖是两个 class 可以相互交互的多种方式之一。正确的做法是通过使它们远离彼此显式交互来使它们松散地相互耦合,这使您可以独立地改变 classes。请查看中介模式。在下面的示例中,class C 是一个逻辑中介。

class A {
    public:
        void Do2() {
            //do something
        }
};

class B {
    public:
        void Do1() {
            //do something
        }

};   


 class C
    {
      private A a;
      private B b;
      C(A a, B b)
      {
         this.a = a; this.b = b;
      }

      public void A_do1 { b.do1(); }
      public void B_do2 { a.do2(); }
    }