Decoupling Techniques: How Do I Get That Rubber Band in the Middle?
How do you know when a system is tightly coupled or not? In this post, I discuss why it's so important to decouple your code and how to identify and fix the problems.
Every developer is looking for that holy grail of an ultimate software system that is entirely decoupled.
There are a number of ways to decouple a system. However, if you don't have the experience to identify one, it may take some years to immediately know when a website or system is tightly coupled.
Custom applications can look like our rubber band ball shown above. I need the rubber band (method) from the middle and I don't know how to get it?
Today's post discusses how to identify if a system is too tightly coupled and make it more open.
Take it From The Top
When a system is tightly coupled, it describes the relationship between two entities (usually classes) in a software system. If one class knows too much about the other class, it's considered tightly coupled.
When class A starts to know too much about class B, then it's time to start examining the code.
But how do you know if a system is tightly coupled or not?
Tips for Decoupling Software
Take a handful of developers and throw them into a room and based on experience, every one of them will have a different way of decoupling the software.
Here are some tips on what to look for when identifying whether code is tightly coupled or not.
Internal or Sealed Access Modifiers
Internal access modifiers are accessible only within files in the same assembly where sealed access modifiers are classes that prevent other classes from inheriting from it.
Over my years of development, I have never used Internal or Sealed (yes, I know what they do).
In my opinion, if you have one, it causes some issues. If you have both (an internal and a sealed), it just makes matters worse.
Let's say you have this piece of code (don't laugh...this is in production...somewhere):
internal sealed class Address { public string Address1 { get; set; } public string Address2 { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } public string Country { get; set; } }
Joe-programmer comes in on his first day on the job and has to inherit to make a new Address class. How do you fix this issue without modifying the class and ruining all of the unit tests?
You work around it. You build a wrapper, add an interface to make it less decoupled, and hide the workings of the Address implementation until you can refactor it later.
public class PolicyAddress: IPolicyAddress { private Address _address; public PolicyAddress() { _address = new Address(); } public string Name { get; set; } public string Address1 { get { return _address.Address1; } set { _address.Address1 = value; } } public string Address2 { get { return _address.Address2; } set { _address.Address2 = value; } } public string City { get { return _address.City; } set { _address.City = value; } } public string State { get { return _address.State; } set { _address.State = value; } } public string Zip { get { return _address.Zip; } set { _address.Zip = value; } } public string Country { get { return _address.Country; } set { _address.Country = value; } } } public interface IPolicyAddress { string Name { get; set; } string Address1 { get; set; } string Address2 { get; set; } string City { get; set; } string State { get; set; } string Zip { get; set; } string Country { get; set; } }
While this isn't the best approach, it gets the class built and is workable until a better refactoring can happen (hopefully in the not-too-distant future). ;-)
Dependency Injection (DI) or Inversion of Control (IoC)
Lately, this has been the mantra of every developer worth their salt. I mentioned a while back that I was using a Dependency Injection library called Ninject, but after some interesting news about performance issues, I'm moving towards StructureMap now.
In my opinion, dependency injection can be described in two words: Decoupled Factories. Any DI library can immediately enhance your development so long as you are following the rules of development by using interfaces.
The idea behind DI is that you are letting your DI library know how to create an instance of an object by telling it which interfaces are attached to which concrete classes.
There is the drawback of having no interfaces in your code. If you don't have interfaces in your code, then you really don't need...no, can't use, dependency injection.
Simpler (and smarter) Access to Objects
Have you ever seen this code before?
Order.Customer.Address.City
This code violates the Law of Demeter. The Law of Demeter for functions requires that a method m of an object O may only invoke the methods of the following kinds of objects:
- O itself
- m's parameters
- Any objects created/instantiated within m
- O's direct component objects
- A global variable, accessible by O, in the scope of m
So if we follow our guidelines and refactor the example above, we come up with:
var name = Order.GetCity();
The general rule is to only "Use one dot."
If you need a more concrete example, I would recommend reading The Paperboy, The Wallet, and the Law of Demeter (pdf).
Unit Tests
I've said before that unit tests show more than just a red/green passing grade. Write your unit tests like you are writing production code. Envision how you see the code working and write it as such in your unit tests.
When you're developing an application, unit tests can show you how simple or how complex you can build a system.
Once you have those concepts in your unit tests (and they pass), you can transfer the concept over to real code...and the best part is that your code is all ready to go because it's been tested.
Conclusion
As you can see, there are many ways to take existing code and refactor it into something more usable.
I see programmers with 15 to 20 years of experience expanding their career path even further by adding new skills of identifying and refactoring coupled software into flexible, easily maintainable code for future reuse.
Decoupling and refactoring software will become more of a required talent.
Do you have any more techniques on how to decouple software? Post them in the comments below.