MixMix
MixMix

Reputation: 99

Design: Class circular dependency?

I've been reading different answers here about solving the circular dependency in software design and I still can't get it. Please help me understand it.

Let's say I have class A and class B which call methods of each other:

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

Classes are perfectly divided according to Single responsibility principle.

It looks like I can't apply Dependency Inversion Principle here as I can aggregate only one class into another.

According to best design practices could you suggest me the proper way on how to handle this?

I'd appreciate your answers with some code.

Upvotes: 4

Views: 2338

Answers (2)

Larry
Larry

Reputation: 2252

You need to remove the circular dependency by put them into another class. From high level, the circular dependency is one of the many ways two classes can interact with each other. The proper way to do is make them loosely couple with each other by keeping them away from interacting with each other explicitly, this let you to vary the classes independently. please check out mediator pattern. In the following example, class C is a logical mediator.

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

Upvotes: 4

T.S.
T.S.

Reputation: 19384

Lets start with overview of circular dependency given by Wikipedia

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...

Lets break it:

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

It says Natural. Here is an example from Ado.Net. DataTable And DataRow objects

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

In this example you have a subsystem where CD is, as they said - natural. For example, you know how they have VIN number on every major part of your car. Lets achieve it in pseudo code

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

Lets just imagine that someone pulled your door off, police recovers it and you need to identify is it your door or someone else's - then you do:

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

Idea: in subsystems that work closely together and organized together, CD are sometimes OK

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

Your application can be physically located in the same assembly (dll, exe) while logically it can have separate logical layers. For example, you can write your UI, BLL and IO in the same assembly. Therefore it will be easy to create circular dependency between, lets say, your UI and BLL. It will be much harder to do if each layer lived in a separate assembly.

Often CD is confused with tight coupling. While in example above we saw how useful CD can be sometimes, tight coupling usually means that one concrete object knows another concrete object and there is no room to re-usability, expandability, testability. Lets go back to car. Lets get into details of hanging doors on the car - your car goes through assembly line and it came to door attaching machine

class DoorHandler{

    private Car _car;
    private MitsubishiDoorAlignmentMachine _doorMachine;        

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

}    

But now we have a problem - our Door handler works only with MitsubishiDoorAlignmentMachine. What if I want to replace it with FujitomoHeavyIndustriesDoorAlignmentMachine? Then I need to do something like this:

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

This way my DoorHandler is not dependent on any specific machinery

Now, lets go back to your situation - A and B. There could be multiple answers. If you're creating subsystem in which you need to have interaction between objects but there could be multiple implementations - just use interfaces. This way your A and B will know the signature and not a concrete object. If there is only one implementation and objects are part of same subsystem in the same assembly - knowing each other may be just OK.

Upvotes: 2

Related Questions