0

Class and component dependency cycles Assume, we have component A with class CA, and component B with class CB, with a cyclic two-way dependency between classes CAand CB. Hence, we have a cyclic dependency between components A and B.

enter image description here

Resolving class dependency cycle A standard way to resolve the class cycle is to introduce interfaces (e.g. IA implemented by CA in A and IB implemented by CB in B) and let the classes depend on the interfaces. Now, there are no dependency cycles between the types, but still between components.

enter image description here

Resolving component dependency cycle On frequently suggested idea (e.g. by Robert Martin) is to move one of the interfaces to the other component, e.g. component A with class CA and component B with class CB and both interfaces.

enter image description here

Issues with the approach For somebody who is used to have interface and class in the same component, this feels unnatural because they seem to belong together. Probably I am thinking too much in terms of what services a class is offering and then extracting the interface instead of what services consumers require and let the implementation be completely independent in my mind.

Question I am looking for more reasons why it is a good idea split up interface and implementing class into separate components?

0

4 Answers 4

4

For somebody who is used to have interface and class in the same component, this feels unnatural because they seem to belong together.

This is where you need to shift your mindset. Instead of thinking "IA defines what CA implements, therefore they belong together", think "IA defines what CB expects of it's collaborators, therefore, they belong together."

Think that IA is part of CB's public API. Just like IB describes API that CB provides to the outside world. So does IA describe what CB expects from the outside world.

This shift in mindset then makes it clear that IA and CB belong together. And it also makes it more likely there will be different implementations of IA, as IA and CA are no longer tightly coupled.

1
  • Thank you, I am beginning to understand the required shift in mindset. How would I extend that approach if IA is also required by another class CC, which is in component C != B? Add an extra interface component as suggested by DocBrown for solving the bidirectional independency problem?
    – dn1h
    Commented Aug 3 at 8:14
2

Creating interfaces for classes as well as placing them in a certain component is not an end in itself. It creates extra effort and boilerplate code, which needs to be justified by some purpose.

So the first question you need to ask yourself here is: which purpose do you want to achieve? For example, unit testing is a typical goal. But unit testing alone the scetched classes does only require interfaces, not necessarily in separate components. Still, isolation at the component level can make testing more simple or faster or less costly, or it enables parallel development by different persons or teams. Hence you need to decide if you want the classes CA and CB testable in full isolation from each other, so tests for CA, which may need to implement a mock for CB using the interface IB dont even have to include the component B? Or is including the component B for a test cheap and hence acceptable?

If you think you need full isolation in both directions, none of your designs seems to be sufficient - for this, one will have to introduce extra interface components, either one layer for all interfaces, or an individual interface component for each interface. But beware, such a split-up causes extra maintenance effort, hence you should have a real need for it.

If in doubt, start with the most simple approach which serves your current needs (for example, everything in just one component), and refactor when you encounter requirements where separate components seem better suited.

1
  • Thank you, defining a clear goal will certainly help!
    – dn1h
    Commented Aug 3 at 8:14
2

Interfaces should be grouped with what owns then. Ownership is about what can force change.

If A uses B through I the real question is if you remove method call b.x() from A but not x() from B then what should happen to x() in I? Group I with the one whose changes it follows.

I call this ownership. An interface defines a communication relationship between objects. But that relationship is rarely symmetrical. Usually the interface either represents a using objects needs or an implementing objects mini language (at least one of them). The difference is which one can force I to change.

I’ve talked about this before here.

1
  • Thank you, the referenced article was very helpful.
    – dn1h
    Commented Aug 4 at 4:21
0

In C or C++, you can just declare a struct (or a class in C++). This gives almost no information about the struct or class, except that such a class exists. You can have pointers to struct or class instances and pass them around, and that's about it.

Now that is enough for example to just declare the existence of two classes C1 and C2, and then define them and note that an instance of C1 contains a pointer to a C2 instance, and an instance of C2 contains a pointer to a C1 instance. You can declare C1 methods that take pointers to C2 instances as arguments or return these pointers. You can do that because your compiler can compile your code without having the slightest idea what a C2 instance looks like.

In many cases this is enough to handle circular references. And it's often a good idea anyway. If C1 has a method that is given a pointer to C2 by a caller, and passes it on to someone else, C2 has no need to know the details of C2, so it shouldn't have knowledge of these details.

With interfaces, it is often the case that you know how interfaces are related, but not how classes implementing the interface are related. So if a class implementing I1 refers to I2, that's not a problem whatsoever. If you have circular references between interfaces themselves, you most likely have a more modern compiler which can handle it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.