What is "loose coupling?" Please provide examples
Consider a simple shopping cart application that uses a CartContents
class to keep track of the items in the shopping cart and an Order
class for processing a purchase. The Order
needs to determine the total value of the contents in the cart, it might do that like so:
Tightly Coupled Example:
public class CartEntry
{
public float Price;
public int Quantity;
}
public class CartContents
{
public CartEntry[] items;
}
public class Order
{
private CartContents cart;
private float salesTax;
public Order(CartContents cart, float salesTax)
{
this.cart = cart;
this.salesTax = salesTax;
}
public float OrderTotal()
{
float cartTotal = 0;
for (int i = 0; i < cart.items.Length; i++)
{
cartTotal += cart.items[i].Price * cart.items[i].Quantity;
}
cartTotal += cartTotal*salesTax;
return cartTotal;
}
}
Notice how the OrderTotal
method (and thus the Order class) depends on the implementation details of the CartContents
and the CartEntry
classes. If we were to try to change this logic to allow for discounts, we'd likely have to change all 3 classes. Also, if we change to using a List<CartEntry>
collection to keep track of the items we'd have to change the Order
class as well.
Now here's a slightly better way to do the same thing:
Less Coupled Example:
public class CartEntry
{
public float Price;
public int Quantity;
public float GetLineItemTotal()
{
return Price * Quantity;
}
}
public class CartContents
{
public CartEntry[] items;
public float GetCartItemsTotal()
{
float cartTotal = 0;
foreach (CartEntry item in items)
{
cartTotal += item.GetLineItemTotal();
}
return cartTotal;
}
}
public class Order
{
private CartContents cart;
private float salesTax;
public Order(CartContents cart, float salesTax)
{
this.cart = cart;
this.salesTax = salesTax;
}
public float OrderTotal()
{
return cart.GetCartItemsTotal() * (1.0f + salesTax);
}
}
The logic that is specific to the implementation of the cart line item or the cart collection or the order is restricted to just that class. So we could change the implementation of any of these classes without having to change the other classes. We could take this decoupling yet further by improving the design, introducing interfaces, etc, but I think you see the point.
Many integrated products (especially by Apple) such as iPods, iPads are a good example of tight coupling: once the battery dies you might as well buy a new device because the battery is soldered fixed and won't come loose, thus making replacing very expensive. A loosely coupled player would allow effortlessly changing the battery.
The same goes for software development: it is generally (much) better to have loosely coupled code to facilitate extension and replacement (and to make individual parts easier to understand). But, very rarely, under special circumstances tight coupling can be advantageous because the tight integration of several modules allows for better optimisation.
I'll use Java as an example. Let's say we have a class that looks like this:
public class ABC
{
public void doDiskAccess() {...}
}
When I call the class, I'll need to do something like this:
ABC abc = new ABC();
abc. doDiskAccess();
So far, so good. Now let's say I have another class that looks like this:
public class XYZ
{
public void doNetworkAccess() {...}
}
It looks exactly the same as ABC, but let's say it works over the network instead of on disk. So now let's write a program like this:
if(config.isNetwork()) new XYZ().doNetworkAccess();
else new ABC().doDiskAccess();
That works, but it's a bit unwieldy. I could simplify this with an interface like this:
public interface Runnable
{
public void run();
}
public class ABC implements Runnable
{
public void run() {...}
}
public class XYZ implements Runnable
{
public void run() {...}
}
Now my code can look like this:
Runnable obj = config.isNetwork() ? new XYZ() : new ABC();
obj.run();
See how much cleaner and simpler to understand that is? We've just understood the first basic tenet of loose coupling: abstraction. The key from here is to ensure that ABC and XYZ do not depend on any methods or variables of the classes that call them. That allows ABC and XYZ to be completely independent APIs. Or in other words, they are "decoupled" or "loosely coupled" from the parent classes.
But what if we need communication between the two? Well, then we can use further abstractions like an Event Model to ensure that the parent code never needs to couple with the APIs you have created.