0

I made an extensive research in previousv related questions, but since my questions is somewhat peculiar, I decided to create this new one.

I am implementing a visualisation application (in JS). There is a class, called Graph, that contains the visualisation of a graph, along with all the necessary components (nodes, edges etc.). I decided to implement a clustering functionality over the Graph, that enables the user to cluster a group of nodes to a single cluster, depending on some attributes. I initially implemented this functionality in the Graph class. However, since it is a significant amount of logic, that is not directly related to the Graph, I think it should be properly refactored to a different component (I called it ClusteringEngine). And here comes the issue.

ClusteringEngine needs a reference to Graph, in order to retrieve nodes, edges etc. So, if ClusteringEngine is implemented as a sub-component of an outer class (a CentralController), the dependency graph would be like:

CentralController ---> Graph <----\
                 \                 \
                  \--> ClusteringEngine

which seems highly coupled. Another option is to implement ClusteringEngine as a component of Graph, where I will end up with a circular reference:

CentralController -----> Graph <-----> ClusteringEngine

which is again highly coupled. I was wondering if there is a way to avoid this problem and design it in a better way.

P.S. I have thought of the Observer Design Pattern, but since there is no context of events-messages, I think it is not so appropriate for this case.

2
  • 1
    You haven't explained why graph needs a reference to the ClusteringEngine Commented Jun 21, 2016 at 11:34
  • Graph needs a reference to ClusteringEngine, only in the second case, in order to pass method calls from CentralController to ClusteringEngine. Commented Jun 21, 2016 at 11:58

2 Answers 2

3

which seems highly coupled.

It's not highly coupled. In fact, it's the minimum coupling state. The ClusteringEngine will always depend on the Graph, no matter what you do. Furthermore, either the CentralController or the Graph must depend on the ClusteringEngine, else there won't be any clustering in the system. And finally, one considers that if the CentralController is handling e.g. user input, and the user input can cause the clustering, it's also totally unavoidable for the CentralController to depend on the ClusteringEngine.

Between these two choices, it's clearly better for the CentralController to depend on the ClusteringEngine. It's not circular and it's quite reasonable for the central controller to depend on things to make things happen, and it basically has to have that dependency anyway.

There's nothing wrong with your first alternative.

3
  • After a bit of consideration, I almost ended up in the same conclusion. The reference of Graph, that ClusteringEngine needs, is necessary, since it operates over it. Thanks. Commented Jun 21, 2016 at 12:00
  • Graph is likely the most stable element in this design. High coupling towards stable things is fine. Commented Jun 21, 2016 at 20:15
  • "which seems highly coupled" is correct, other than replacing "highly" with "strongly" or "tightly" to be semantically accurate. This is "Pathological" / "Content" Coupling, not to mention [assumably] Data Coupling and potentially others -- but Content Coupling is one of the strongest types of Coupling one can implement. Your statement that "it's the minimal coupling state" is simply, flat-out incorrect. Did you mean to say that the Fan-Out/-In / In-/Out-Degree is minimal? Commented Sep 26, 2018 at 17:22
1

The standard way of resolving circular references (and removing coupling) in object oriented systems is dependency inversion. To do this, we'd take one of your concrete classes (most likely the ClusteringEngine, as it seems the most self-contained, but theoretically at least you could do this for your Graph instead), and declare an interface for it. All of the existing code is changed to use the interface, and an instance of the concrete ClusteringEngine (or a factory object that can produce them, if you require more than one) is then provided to the code from an external source, probably by using some form of dependency injection. The idea of using this process to remove coupling from high level components to lower level ones forms the dependency inversion principle, which is the D of the SOLID design principles.

As you're using a dynamic language, there's no need (or ability) to literally declare an interface as such, but it would be a good idea to document in the documentation of your graph object the protocol that is used to communicate between it and the clustering engine, and how the clustering engine is injected into it (e.g. via a constructor parameter).

4
  • 2
    The circular reference would still exist, just now in interfaces. Commented Jun 21, 2016 at 11:32
  • @DeadMG - I don't think it would. Graph would then depend on an entirely abstract interface/protocol, while ClusteringEngine would depend on Graph. Commented Jun 21, 2016 at 11:33
  • 2
    So... how is the ClusteringEngine going to get that Graph reference? Commented Jun 21, 2016 at 11:35
  • 1
    @Jules, this is a great answer. Why in the world would Jules' answer be downvoted????????? This answer is solid ... even SOLID. Moreover, I agree that Jules' approach should be able to Decouple the dependency cycle. I'm upvoting this to compensate for the lunatics that downvoted it, and because it deserves an upvote. Commented Sep 26, 2018 at 17:30

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.