Insphpect

What is Tight Coupling?

Coupling, with objects, is much like "Coupling" with people, it describes how a pair of objects interact 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) {
        
$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 the instance is 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.

Real world example

The above example is very contrived to prove a point but in real programs loose coupling 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 saves 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, the class must be edited. This prevents me having two instances of the Signup class that write to different files. It's not unreasonable that the system could be extended to have have multiple newsletters that the user wants to sign up to, if all the signups are stored in the same file it wouldn't be possible to 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 an instance of Signup is created, the file name can be set to the file for the particular newsletter they want to sign up to:

 

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

This is immediately more flexible but it's still not perfect. Currently, signups can only be written to a file because the constructor instantiates a File instances. Instead, if the instance was passed in using dependency injection it would be possible to use any storage mechanism. For example, swapping out file storage for database storage:

 

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 almost exactly the same, the only difference is that the coupling of the $storage instance has been changed the coupling from tight to loose. The signup class still requires a storage mechanism but it isn't coupled to the specific implementation that writes to a file. This allows using the Signup class with any storage mechanism:

 

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'));
new 
Signup(new RESTApi('rest.example.org'));

Other forms of tight coupling

In addition to using the new keyword as above, there are other ways of introducing tight coupling into Object-Oriented code.

Inheritance

Inheritance is another method of tightly coupling classes. When using inheritance, it's impossible to substitute the base class at runtime and the relationship is incredibly rigid.

The above examples could be modeled using inheritance as so:

 

class Dave extends Kate {
    public function 
date() {
        
parent::takeOut();
        
// or $this->takeOut();
    
}
}

Whenever the date() method is called, the takeOut() method from the Kate class is called and there is no way of substituting this without rewriting the class. Once again, Dave can only date Kate because they are tightly coupled.

The Signup class could also be modeled using inheritance:

 

class Signup extends File {
    public function 
process() {
        
$signup = new stdClass;

        
$signup->name $_POST['name'];

        
$signup->email $_POST['email'];

        
//call the writeObject method from the parent class
        
$this->writeObject($signup);

    }

}

The same problem occurs here. The only writeObject implementation that can be used without rewriting the class is the implementation in the File class because of the tight coupling introduced with inheritance. There is no way to substitute the implementation at runtime.

Although it's possible to override the writeObject method in the Signup class, the Signup class would still be locked to a single implementation (the one in the Signup class). There would be no way to replace the implementation without changing the code in the class or adding another subclass (which itself would be tightly coupled to its parent class).

Inheritance and using new in a constructor are identical in the way the limit flexibility and in the way they force your class design. They both prevent runtime substitution and require modifying the code in the class to alter the execution of the method.

Static Methods

Tight coupling is also introduced via static methods. The above examples could also be re-written using static methods:

 

class Dave {
    public function 
date() {
        
Kate::takeOut();
    }
}

 

class Signup {
    public function 
process() {
        
$signup = new stdClass;

        
$signup->name $_POST['name'];

        
$signup->email $_POST['email'];

        
File::writeObject($signup);
    }

}

Static methods introduce exactly the same problem as inheritance and using the new keyword. There's no way to substitute where the data is being written without changing the code.

How to fix it?

The fix, for all three of these issues is to use loose coupling by using dependency injection:

 


class Dave {
    private 
$partner;

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

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



and

 

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);

    }

}

The person being dated can be provided at runtime:

 

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


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


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


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

and the Signup class can be configured at runtime, allowing multiple instances to exist with completely different configurations:

 

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'));
new 
Signup(new RESTApi('rest.example.org'));

This is much more flexible as:

How to identify tight coupling

Tight coupling exists whenever a class named is explicitly used inside another class. Whether that's a static method call, extends or the new keyword. The result is the same, the relationship between the two classes is rigid and it's impossible to substitute the dependency for a different implementation.

Conclusion

When defining relationships between classes there are multiple approaches which can be taken: instantiating one object inside another, inheritance, static methods, singletons or dependency injection.

Dependency Injection is by far the most flexible as it doesn't define a hardcoded relationship between the classes and objects can be used with different dependencies.

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

By using Dependency Injection, the object can be instantiated with as many different configurations as required and isn't tied to a specific implementation. With tight coupling, the configuration is explicitly defined inside the class and there is no way to override it.

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