Monday, 16 June 2008

What is Loose Coupling?

« How REST will neuter Web Services | Main
I see many people throwing the term "loose coupling" around without understanding it. Many use the term without reference. Worse, there seems to be a sophomoric misinterpretation of it which can be summarized as "tight coupling is always bad".

Despite the fact that speak about loose coupling, there are precious few good descriptions of exactly what it is. Fortunately, Wikipedia (as of the writing of this blog) gets the definition right, defining loose coupling as "an approach where integration interfaces are developed with minimal assumptions between the sending/receiving parties, thus reducing the risk that a change in one application/module will force a change in another application/module". The words I emphasize are "minimal" and "risk". The word minimal cannot be replaced with "no". Also, loose coupling is is about mitigating risks of change, and you must not gloss over this. Even Wikipedia could explain these points more.

To really understand loose coupling, we must first acknowledge that as designers, we are essentially gambling on the types of changes our system will be asked to adapt to in the future. We form a hypothesis about cost and likelihood of changes to various design elements, and we split them into two classes: stable or fluid. We expect that the stable elements are unlikely enough to change over time that as an economics question we're better off assuming they won't, while the fluid elements we draw the opposite conclusion. Sometimes designers make these hypothesis explicitly, while others make them implicitly. Different designers can look at the same problem and draw different hypotheses about change, as predicting future requirements is not an exact science. Once we write code based on our hypothesis, we often say that the fluid elements are implementation details, while the stable elements are part of the design contract or interface.

Here's my definition of "loose coupling". Concrete integrated elements are "loosely coupled" with respect to a hypothesis about the risks of change, when the stable elements under the hypothesis are relied on by the interface design contracts, so that the hypothesized changeable elements are expressed only in the implementation details. We typically introduce tight coupling of stable elements purposefully so as to tolerate specific kinds of fluidity among the implementation details. The notion that tight coupling is always bad, is nonsense. Tight coupling to a few things that won't change is generally good.

To illustrate our definitions, let's consider an example. What does coding to interfaces in Java achieve? We introduce an interface so that we can switch out the implementation behavior. We are relying on the methods of the interface and their arguments and return types not changing. If we expect to actually have multiple implementations, this is clearly a good idea. If we never get a second implementation, we just engaged in over engineering, as we spent time creating two artifacts (the interface and it's lone implementation) for a benefit that never materialized.

Consider another example: when we use a SOAP web service interface, we couple to the HTTP protocal and XML standards, the SOAP and WSDL standards and schemas, as well as a homegrown XML Schema for our domain, and the operations and faults defined in the WSDL. In return, for all this tight coupling, we can remove any dependence on any platform or language specific constructs, let alone any specific implementation classes. It's usually a pretty good bet that things like HTTP, XML, SOAP, and WSDL change very little over time, though I look forward to WSDL 2.0. It's predecessor was a little over-engineered, offering too much flexibility at the expense of complexity.

Every time we create a solution, we gamble. Over time, our change hypothesis is repeatedly tested as our code is maintained and implements new requirements. We can compared, with the benefit of hindsight, the kinds of changes we've actually performed with how we predicted. There are four possibilities...

  • predicted change, observed change. Good.
  • predicted change, observed stable. This is the "over-engineering" mistake
  • predicted stable, observed stable. Good.
  • predicted stable, observed change. This is the "tight coupling" mistake.
There are costs associated with each mistake, and when you place your bets you have to weigh the likelihood and the expense of the above possibilities. The cost of having to make changes across tightly coupled elements are well known. The change is invasive, it may take a lot of work to determine everything in the dependency chain, it's easy to miss dependencies, it's harder to assure quality, and deployment are harder, etc... . On the other hand, the costs of over-engineering are bad too: code bloat, complexity, slower development, etc... .

Let's examine ordinary class encapsulation using the approach of this article. Encapsulation is a loose coupling technique applied to class design in an object oriented language. Classes define a public contract to their collaborators via their publicly declared methods. Regardless of whether the class implements a formal interface or is directly called by client code, the method arguments and return types will be depended upon. In fact, using an interface INCREASES the coupling to these, as the interface itself, all of it's implementations, and any client who called one of these methods (which is a larger set because of the abstraction) all must be modified if the method signature changes. We should weigh the odds and impacts and choose to expose methods with arguments and return types where your hypothesis is they are stable. So if we expect to always have Customers who have names that are Strings, by all means have customer.getName() return a String. On the other hand, if this might change, introduce a CustomerName object and/or interface to encapsulate the implementation details of names and expose this rather than String. Is this approach always better? No, it depends on your assessment of the future, and you simply can't make universally sweeping statements that one is better than the other.

As I mentioned, different designers can look at the same problem and assess the risks differently. There are two styles that seem to emerge, and which camp you are in seems to depend on how you weigh the pain of complexity against the pain of refactoring. Some people will not introduce loose coupling until it's clear there is a need for changes to implementation details. These people say things like "the simplest thing that works" and "you aren't going to need it". Others take their best guess based on the available knowledge and their experience and place their bets, sometimes introducing abstractions early. I liken these to the different styles of poker player: some people play tight and some play loose. Neither is "correct" in any objective sense. We've all seen bad examples of taking either too far: the guy who uses several design patterns to avoid relying on the fact that customer.name is a String, or the guy who simply refuses to code to interfaces and has methods that return HashMap instead of Map and so on. Either extreme can be bad. Hopefully now that we understand what loose coupling really is, the reader won't make either mistake.

Technorati Tags:

Posted by spout at 1:03 AM in stuff about java

 

[Trackback URL for this entry]

Pingback: Abstraction » What is Loose Coupling? at Tue, 17 Jun 1:59 AM

What is Loose Coupling?
is Loose Coupling? …implementations, and any client who called one of these methods (which is a larger set because

Your comment:

(not displayed)
 
 
 

Live Comment Preview:

 
« June »
SunMonTueWedThuFriSat
1234567
891011121314
15161718192021
22232425262728
2930