Tom Butler's programming blog

Slutty Software is good software: Tight and loose coupling in OOP

Coupling in OOP

It's valentines day so I thought I'd write a seasonal post on coupling. Coupling, in OOP is inevitable. One component will always need to communicate with others in some way. Without coupling either nothing can communicate or everything in the whole application must exist in the same class. Clearly neither of these are viable solutions.

Coupling, with objects is much like "Coupling" with people, it descibes how a pair of objects interract with one another.

There are two different types of coupling: Tight coupling and Loose coupling. These describe the different types of relationships between objects.

A married couple can be said to be "tightly coupled" because they are only allowed to date one another. A single person can date anyone they like. In php the married couple who are tightly coupled can be demonstrated as:

class Dave {
    private 
$partner;
    
    public function 
__construct() {
        
$this->partner = new Kate;

    }

    public function 
date() {
        
$this->partner->takeOut();        
    }
}

Here, Dave can only ever take Kate out on a date. Whenever $dave->date() is called, the takeOut() method will be called on an instance of Kate. There is no way for Dave to take a different partner on a date.

$dave = new Dave();
$dave->date();

This is the essence of tight coupling. Dave is tightly coupled to Kate because there is no way for Dave to take anyone else on a date in this code

Bob, however, represents a single person:

class Bob {
    private 
$partner;
     
    public function 
__construct(Partner $partner) {
        
$this->partner $partner

    
}

    public function 
date() {
        
$this->partner->takeOut();        
    }
}

There's only one difference between Dave and Bob, and that is, Bob can have his parter assigned when he's created using Dependency Injection. This gives Bob more flexibility because he can go on a date with anyone!

$bob = new Bob(new Amy);
$bob->date();

$bob = new Bob(new Kate);
$bob->date();

$bob = new Bob(new Dave);
$bob->date();

$bob = new Bob(new Bob);
$bob->date();

This is the difference between tight and loose coupling in OOP.

Flexibility

You can see how this is more flexible in this example, but in real programs this is incredibly useful as it allows us to build code that can work with different collaborators. Consider a program that was a newsletter signup form. It takes some data from the user via $_POST and save it to a file:

class Signup {
    private 
$file;
    
    public function 
__construct() {
        
$this->file = new File('./signups.txt');
    }

    public function 
process() {
        
$signup = new stdClass;
        
$signup->name $_POST['name'];
        
$signup->email $_POST['email'];
        
$this->file->writeObject($signup);
    }
}

The file class here has a writeObject method that takes an object and writes it to a file. How that works isn't really important for this example but assume it writes it in JSON: {"name": "Bob", "email": "example@example.org"}

One immediately obvious problem with this approach is that it can only ever write to signups.txt. To change the file being written to I must edit the class. This prevents me having two instances of the Signup class that write to different files. It's not unreasonalbe that I might have multiple newsletters that the user wants to sign up to, if I store all that in the same file I won't know which user wants to sign up for which newsletter

Of course this is easily fixed:

class Signup {
    private 
$file;
    
    public function 
__construct($filename) {
        
$this->file = new File($filename);
    }

    public function 
process() {
        
$signup = new stdClass;
        
$signup->name $_POST['name'];
        
$signup->email $_POST['email'];
        
$this->file->writeObject($signup);
    }
}

Now when I create an instance of Signup I can set the file name to be the file relevant to the particular neweletter they want to sign up tp:

new Signup('./news.txt');
new 
Signup('./specialoffers.txt');

This is immediately more flexible but it's still not perfect. What if I wanted to change the implementation to store the signups in a database instead of a file? Because the Signup object is tightly coupled to the File object it's not possible. However, by using loose coupling I can use the same signup class and store the signup data in either a database or a text file:

class Signup {
    private 
$storage;
    
    public function 
__construct($storage) {
        
$this->storage $storage;
    }

    public function 
process() {
        
$signup = new stdClass;
        
$signup->name $_POST['name'];
        
$signup->email $_POST['email'];
        
$this->storage->writeObject($signup);
    }
}

This code is exactly the same I've just changed the coupling from tight to loose. The signup class still requires a storage mechanism but it isn't coupled to a specific implementation that writes to a file. This allows me to do this:

new Signup(new File('./news.txt'));
new 
Signup(new File('./specialoffers.txt'));
new 
Signup(new Database('127.0.0.1''username''password''tablename'));
new 
Signup(new Database('127.0.0.1''username''password''another_table'));

Or use an XML file, a CSV file, post to a web service, a twitter feed, etc. As you can see this adds a great deal of flexiblity by simply moving a single line from inside the constructor to outside it.

Conclusion

Loose coupling makes your program far more flexible with little to no extra effort required. Using loose coupling, as the requirements of your project inevitably change it's very easy to implement the updates. With tight coupling it can be incredibly difficult, especially when you need to maintain two methods of doing the same thing (e.g. having the signup write to a file in one place and a database in another.)

Considering this very useful benefit and zero extra development time or drawbacks, using loose-coupling is a no-brainer! Slutty software is good software!