Coupling is a rough measure of how much one software module relies on another. In particular, "loose coupling" refers to a module that uses only a published interface to another module. Tight coupling, on the other hand, depends on internal details.
Tight coupling is a bad thing because it means that every time someone decides to change the implementation of his or her module, every tightly coupled module might have to be changed, too. For instance, in Example 1(a) a loosely coupled module will only use getAge(). Should a module "cheat" and access the age instance variable directly, then when Example 1(b) is distributed, the cheating module will no longer even compile.
<b>(a)</b> class Person { int age; public int getAge() {return age;} } <b>(b) </b> class Person { int birthYear; public int getAge() {return thisYear - birthYear;} }
Loose Is Better, Always
What you often hear about is the necessity to couple more tightly "for performance reasons." Indeed, it is not difficult to come up with examples of where tight coupling lets naïve compilers produce significantly better codebut you shouldn't use naïve compilers for production code.
The one good thing about this kind of tight coupling is that the compiler prevents you from distributing nonworking code by accident. Which brings us to insidious tight coupling (ITC).
ITC is the situation where one module depends on another module having some special state, or set of string literals, but where the compiler doesn't know. In this case, it is easy to make changes to one module and miss another module that was making assumptions about the first.
An example would be where you know that students are to be entered into a list in grade-point order. If another module relied upon this, then it would fail should the students be entered in a different order. The appropriate way to deal with this situation is to make your code reflect the requirements you put upon it. In this case, you would subclass List with a class SortedList, which would throw an exception if someone tried to insert a student out of order.
The most common example of ITC is when two modules need to use the same string literal. Within a single language project, this issue is resolved through the use of canonical objects and constants. But the trouble starts when those modules are in different languages. There's no compiler to tell you when you misspell a variable name, or that you only changed six of the seven places a literal was used. Moreover, because the typical use of these strings is for table lookups (request.getParameter("firstName"), for example), a null value might be a legal value, making it much more difficult to find the problem. (Often times, the bug is never noticed and the program simply spits out incorrect data.)
This is ITC at its worst. And where do you typically see many languages coming together? Web apps!
For instance, consider:
- You decide to write the logic in Java.
- 2, 3. You're targeting browsers, so you need HTML and JavaScript.
- You'll certainly use CSS.
- Ajax is popular, which means XML or JASON (or both!)
- You're running Apache, so you'll need an XML DTD for configuration files.
- 7, 8. What good is a web site if you can't make objects persistent? So you'll need SQL/HQL. Maybe you want Hibernate? Those config files count.
- 9, 10. Finally, you'll want to write out a pretty page and you'll need JSP+JSF or something similar.
- HTTP! I forgot HTTP.
That's 11 languages for an elementary application. This is insane. What the heck are we doing? Well, we're letting tools control our lives. We are designing our applications so that they'll fit neatly into Ruby or Tapestry or J2EE. We have all these fancy tools that work for one aspect of creating a web application, then leave us to muddle through the rest. Tools are supposed to serve us. We are supposed to decide what we want, then find the tools that let us do it. We can write software to do anything. Why don't we write software to build entire web applications?