设计:类循环依赖?

时间:2015-03-22 20:50:07

标签: design-patterns aggregation circular-dependency class-diagram dependency-inversion

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

假设我有A类和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();
        }
};

根据单一责任原则完全划分课程。

我似乎无法在此处应用依赖性倒置原则,因为我只能将聚合一个类转换为另一个类。

根据最佳设计实践,您能否告诉我如何处理这个问题?

我很感激您使用某些代码获得答案。

2 个答案:

答案 0 :(得分:3)

您需要通过将循环依赖项放入另一个类来删除它。从高层次来看,循环依赖是两个类可以相互交互的众多方式之一。正确的方法是让它们通过明确地彼此交互而使它们彼此松散地耦合,这让你可以独立地改变类。请检查调解员模式。在以下示例中,类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(); }
    }

答案 1 :(得分:2)

让我们从维基百科提供的overview of circular dependency开始

  

在许多域模型中,循环依赖是很自然的,其中同一域的某些对象彼此依赖。然而,在软件设计中,较大的软件模块之间的循环依赖性被认为是反模式,因为它们的负面影响......

让我们打破它:

  
      
  1. 在许多域模型中,循环依赖是很自然的,其中同一域的某些对象相互依赖。
  2.   

它说自然。以下是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. 较大软件模块之间的循环依赖关系被视为反模式
  2.   

您的应用程序可以物理上位于同一个程序集(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将知道签名而不是具体对象。如果只有一个实现,并且对象是同一个程序集中同一个子系统的一部分 - 相互了解就可以了。