Object-oriented programming languages contend that they are efficient and effective because of the way they model reality. Objects reflect qualities of a real-world problem and the interactions between those objects provide solution. A single object cannot know everything, so inevitably it will have to talk to another object.
If you could peer into a busy application and watch the messages as they pass, the traffic might seem overwhelming. There’s a lot going on. However, if you stand back and take a global view, a pattern becomes obvious. Each message is initiated by an object to invoke some bit of behavior. All of the behavior is dispersed among the objects. Therefore, for any desired behavior, an object either knows it personally, inherits it, or knows another object who knows it.
Because well designed objects have a single responsibility, their very nature requires that they collaborate to accomplish complex tasks. This collaboration is powerful and perilous. To collaborate, an object must know something know about others. Knowing creates a dependency. If not managed carefully, these dependencies will strangle your application.
An object depends on another object if, when one object changes, the other might be forced to change in turn. A dependency is some external object that is relied upon.
For instance, here’s a version of the Gear class, where Gear is initialized with four arguments. The
gear_inches method uses
two of them, rim and tire , to create a new instance of Wheel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Gear attr_reader :chainring, :cog, :rim, :tire def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @rim = rim @tire = tire end def gear_inches ratio * Wheel.new(rim, tire).diameter end def ratio chainring / cog.to_f end end class Wheel attr_reader :rim, :tire def initialize(rim, tire) @rim = rim @tire = tire end def diameter rim + (tire * 2) end end Gear.new(52, 11, 26, 1.5).gear_inches
Try examining the code above and make a list of the situations in which Gear would be forced to change because of a change to Wheel. This code seems innocent but it’s sneakily complex. Gear has at least four dependencies on Wheel ,enumerated below. Most of the dependencies are unnecessary; they are a side effect of the coding style. Gear does not need them to do its job. Their very existence weakens Gear and makes it harder to change.
Listing out the dependencies
In the above example, gear is dependent on wheel because:
- The name of another class:
Gearexpects a class named
- The name of a message that it intends to send to someone other than self:
Wheelinstance to respond to
- The arguments that a message requires:
- The order of those arguments:
Gearknows the first argument to
rim, the second
Each of these dependencies creates a chance that
Gear will be forced to change because of a change to
Wheel . Some degree of
dependency between these two classes is inevitable, after all, they must collaborate, but most of the dependencies listed
above are unnecessary. These unnecessary dependencies make the code less reasonable. Because they increase the chance that
Gear will be forced to change, these dependencies turn minor code tweaks into major undertakings where small changes cascade
through the application, forcing many changes.
Coupling Between Objects (CBO) :
These dependencies couple
Wheel. Alternatively, you could say that each coupling creates a dependency. The more
Wheel, the more tightly coupled they are. The more tightly coupled two objects are, the more they behave like a
If you make a change to
Wheel you may find it necessary to make a change to
Gear. If you want to reuse
comes along for the ride. When you test
Gear , you’ll be testing
What kind of other dependencies are there?
One especially destructive kind of dependency occurs where an object knows another who knows another who knows something; that is, where many messages are chained together to reach behavior that lives in a distant object. This is the “knowing the name of a message you plan to send to someone other than self ” dependency, only magnified. Message chaining creates a dependency between the original object and every object and message along the way to its ultimate target. These additional couplings greatly increase the chance that the first object will be forced to change because a change to any of the intermediate objects might affect it.
Another entire class of dependencies is that of tests on code. The natural tendency of “new-to-testing” programmers is to write tests that are too tightly coupled to code. This tight coupling leads to incredible frustration; the tests break every time the code is refactored, even when the fundamental behavior of the code does not change. Tests begin to seem costly relative to their value. Test-to-code over-coupling has the same consequence as code-to-code over-coupling. These couplings are dependencies that cause changes to the code to cascade into the tests, forcing them to change in turn.
Tight Coupling V/S Loose Coupling
Essentially, coupling is how much a given object or set of object relies on another object or another set of objects in order to accomplish its task. As the name suggests, loose coupling means reducing dependencies of a class that use a different class directly. Loose coupling promotes greater reusability, easier maintainability. On the other hand, tight coupled classes and objects are dependent on one another. I must say that, tight coupling is usually bad because it reduces flexibility and re-usability of code and we are not able to achieve complete object originated programming features.
As par above definition a Tightly Coupled Object is an object that needs to know about other objects and are usually highly dependent on each other’s interfaces. When we change one object in a tightly coupled application often it requires changes to a number of other objects. There is no problem in a small application we can easily identify the change. But in the case of a large applications these inter-dependencies are not always known by every consumer or other developers or there is many chance of future changes.
Think of a car. In order for the engine to start, a key must be inserted into the ignition, turned, gasoline must be present, a spark must occur, pistons must fire, and the engine must come alive. You could say that a car engine is tightly coupled to several other objects.
Loose coupling is simply writing software in such a way that all your classes can work independently without relying on each other. It is a design strategy which allows us to reduce the inter-dependencies between components of a system with the goal of reducing the risk that changes in one component will require changes in any other component. It’s all about thinking a problem in generic manner and which intended to increase the flexibility of a system, make it more maintainable, and makes the entire framework more stable.
Writing Loosely Coupled Code
Every dependency is like a little dot of glue that causes your class to stick to the things it touches. A few dots are necessary, but apply too much glue and your application will harden into a solid block. Reducing dependencies means recognizing and removing the ones you don’t need. Following are some of the methods that prevents an application from strangling itself with tight coupled codes
1) Inject Dependency
Referring to another class by its name creates a major sticky spot. In the code demonstrated before version of Gear we’ve been discussing (repeated
gear_inches method contains an explicit reference to
The immediate, obvious consequence of this reference is that if the name of the
Wheel class changes, Gear’s
method must also change. On the face of it this dependency seems innocuous. After all, if a
Gear needs to talk to a
something, somewhere, must create a new instance of the
Wheel class. If
Gear itself knows the name of the
the code in
Gear must be altered if
Wheel’s name changes.
Gear hard-codes a reference to
Wheel deep inside its
gear_inches method, it is explicitly declaring that it is
only willing to calculate
gear_inches for instances of
Gear refuses to collaborate with any other kind of object, even if
that object has a
diameter and uses gears.
If your application expands to include objects such as disks or cylinders and you need to know the
gear_inches of gears
which use them, you cannot. Despite the fact that disks and cylinders naturally have a diameter you can never calculate their
gear inches because
Gear is stuck to
The code above exposes an unjustified attachment to static types. It is not the class of the object that’s important, it’s the message you plan to send to it.
Gear does not care and should not know about the class of that object. It is not necessary for
Gear to know about
the existence of the
Wheel class in order to calculate
gear_inches . It doesn’t need to know that
Wheel expects to be
initialized with a
rim and then a
tire; it just needs an object that knows
diameter. Hanging these unnecessary
Gear simultaneously reduces
Gear’s reusability and increases its susceptibility to being forced to change
Gear becomes less useful when it knows too much about other objects; if it knew less it could do more.
Instead of being glued to
Wheel, this next version of
Gear expects to be initialized with an object that can respond to
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Gear attr_reader :chainring, :cog, :wheel def initialize(chainring, cog, wheel) @chainring = chainring @cog = cog @wheel = wheel end def gear_inches ratio * wheel.diameter end # ... end Gear.new(52, 11, Wheel.new(26, 1.5)).gear_inches
Gear now uses the
@wheel variable to hold, and the
wheel method to access this object, but don’t be fooled,
doesn’t know or care that the object might be an instance of class
Gear only knows that it holds an object that
diameter. This change is so small it is almost invisible, but coding in this style has huge benefits. Moving
the creation of the new
Wheel instance outside of
Gear decouples the two classes.
Gear can now collaborate with any
object that implements
diameter. As an extra bonus, this benefit was free. Not one additional line of code was written; the
decoupling was achieved by rearranging existing code.
This technique is known as dependency injection. Despite its fearsome reputation, dependency injection truly is this
Gear previously had explicit dependencies on the
Wheel class and on the type and order of its initialization
arguments, but through injection these dependencies have been reduced to a single dependency on the
is now smarter because it knows less.
2) Isolate Dependencies
It’s best to break all unnecessary dependencies but, unfortunately, while this is always technically possible it may not be actually possible. When working on an existing application you may find yourself under severe constraints about how much you can actually change. If prevented from achieving perfection, your goals should switch to improving the overall situation by leaving the code better than you found it.
Therefore, if you cannot remove unnecessary dependencies, you should isolate them within your class.
Isolate Instance Creation
If you are so constrained that you cannot change the code to inject a
Wheel into a
Gear, you should isolate the creation
of a new
Wheel inside the
Gear class. The intent is to explicitly expose the dependency while reducing its reach into your
class. The next two examples illustrate this idea.
In the first, creation of the new instance of
Wheel has been moved from
Gear’s gear_inches method to
method. This cleans up the
gear_inches method and publicly exposes the dependency in the
initialize method. Notice that
this technique unconditionally creates a new
Wheel each time a new
Gear is created.
1 2 3 4 5 6 7 8 9 10 11 12 class Gear attr_reader :chainring, :cog, :rim, :tire def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @wheel = Wheel.new(rim, tire) end def gear_inches ratio * wheel.diameter end end
The next alternative isolates creation of a new
Wheel in its own explicitly defined wheel method. This new method lazily
creates a new instance of
Wheel , using Ruby’s ||= operator. In this case, creation of a new instance of
Wheel is deferred
gear_inches invokes the new wheel method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Gear attr_reader :chainring, :cog, :rim, :tire def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @rim = rim @tire = tire end def gear_inches ratio * wheel.diameter end def wheel @wheel ||= Wheel.new(rim, tire) end end
In both of these examples
Gear still knows far too much; it still takes
tire as initialization arguments and it
still creates its own new instance of
Gear is still stuck to
Wheel; it can calculate the
gear_inches of no other
kind of object. However, an improvement has been made. These coding styles reduce the number of dependencies in
while publicly exposing
Gear’s dependency on
Wheel. They reveal dependencies instead of concealing them, lowering the
barriers to reuse and making the code easier to refactor when circumstances allow. This change makes the code more agile; it
can more easily adapt to the unknown future.
The way you manage dependencies on external class names has profound effects on your application. If you are mindful of dependencies and develop a habit of routinely injecting them, your classes will naturally be loosely coupled. If you ignore this issue and let the class references fall where they may, your application will be more like a big woven mat than a set of independent objects. An application whose classes are sprinkled with entangled and obscure class name references is unwieldy and inflexible, while one whose class name dependencies are concise, explicit, and isolated can easily adapt to new requirements.