Keep Coupling Loose – Object Oriented Development

Keep Coupling Loose – Object Oriented Development

Coupling describes how two modules are related to each other. It is both applicable for classes and routines. The aim is to create visible, direct, small and flexible modules. If one module can easily be used with other modules, this means that the coupling is low enough. Think of the connections of two railroad cars how they can be connected to each other easily by pushing them in opposite ways. If you had to screw things or connect some cables, it could be more difficult to connect and disconnect them. So their connection is simple. Keep the connection between modules as simple as possible.

railroad cars connection
railroad cars connection

For example routine sin():

sin(aDegree);

This function is loosely coupled because it only needs a variable of type degree to produce an output.

initVars(var1, var2, var3, var4, varN);

The routine initVars is more tightly coupled because the module that uses this routine with all the variables it must pass most probably knows what initVars is doing. If two modules depend on a global value, it is even worse.

Coupling Criteria

Some criteria that help to measure the level of coupling are listed below.

Size

The smaller size is better all the time. Size can refer to the number of connections (parameters) that a module needs. A routine needs only one variable is very loosely coupled.

routine(singleVarable);

A routine which runs with 7 variables is not a good sample for a simple connection.

routine(var1, var2, var3, var4, var5, var6, var7);

The size also can be referred to the number of interfaces a module has. A class with 3 interfaces is more loosely coupled than a class with 29 interfaces (public methods).

Visibility

Visibility shows the importance of a connection by showing its details. Programming is not like bean sneaky, it’s more like advertising details and it gains more by advertising. A routine that needs a parameter list to run is good for visibility. If a module needs a modification of a global variable to execute it is sneaky and it is bad. If the global value modification is well documented, it is even better for visibility.

Flexibility

If your module’s connection can easily be changed then it is flexible. It is better to make connections like USB sticks interfaces instead of like connecting with some wires and cables. The flexibility is partly a product of other two criteria. Here is an example below.

Think that you have a routine that calculates the number of vacations that an employee receives each year.

lookupVacationBenefit();

This method only needs two values: hiring date and job classification. Think that the module needs this routine has an object called ‘Employee’ and that object contains those needed two fields. According to this situation, you must pass the ‘Employee’ variable directly to the module for the sake of low cohesion (small sized routine interface, the base module already knows about Employee).

lookupVacationBenefit(employee);

From now on everything looks fine. After a while, think that your another module needs this same method. This module has hiring date and job classification values. So to be able to use the same interface, this module has to create a dummy Employee object and fill only the needed fields to pass its arguments. If you do it this way, this new module will become aware of the routine ‘lookupVacationBenefit’ (It now knows this routine uses Employee object for only two fields hiring date and job classification).

To decrease the cohesion here, the routine must be updated and it must only get needed two parameters instead of an Employee object.

lookupVacationBenefit(hiringDate, jobClassification);

With the new structure, both two modules are not aware of the detail of this routine and cohesion decreases.

While updating the routine interface, we have seen that it was very easy to do it. This is what flexible criteria stand for.

Kinds of Coupling

There three kinds of coupling that you may mostly encounter:

Simple-data-parameter

This happens when two modules are connected with only primitive type parameter list which is acceptable.

simpleDataParameter(aString, anInteger, aBoolean);

Simple-object

If a module instantiates an object it is simple-object coupled to this object. This is fine.

public void routine() {
    SampleObject object = new SampleObject();
    object.setString(aString);
    object.setInteger(anInteger);
    object.setBoolean(aBoolean);
}

Object-parameter

When object1 requires object2 to pass it an object3, they create an object-parameter connection.

object1.routine(object3);

If routine needed only a primitive type list, that would be a weaker connection. Here object1 knows about object3.

Semantic C.

This is the worst scenario. This occurs when module1 makes use of semantic data about module2’s inner workings.

public class Module2 {
    public String text;

    public void printText() {
        System.out.println(String.format("Hello %s!", text));
    }
}
public void routineOfModule1() {
    Module2 module2 = new Module2();
    module2.text = "Java";
    module2.printText();
}

Module1 wants to print a text “Hello Java!” to the console. It knows that moduel2 is going to print a variable in the place of “Java” and modifies that at first. Then calls the routine “printText” to print the desired text. The semantic data of modue2 is modified by module1 to process the desired routine.

Here is another example:

public class BaseObject {
}
public class DerivedObject extends BasObject {
    public void routine() {
    }
}
public class Module2 {
    public void routine2(BaseObject baseObject) {
        DerivedObject derivedObject = (DerivedObject) baseObject;
        derivedObject.routine();
    }
}
public void routineOfModule1() {
    Module2 module2 = new Module2();
    BaseObject derivedObject = new DerivedObject();
    module2.routine2(derivedObject);
}

While module1 passes the derived object to module2 it knows that the routine2 is going to process as a derived object where routine2 accepts a baseObject. The semantic is known again.

If semantic coupling occurs when the written code changes it may break some parts which are completely undetectable by the compiler. In the result of this, being able to debug and detect errors decrease a lot.

As a summary, when we obey the rules described above, our modules create an abstraction and we can write and forget them afterward. To be able to concentrate on a single thing at once is the key part. This decreases the overall complexity which is good where the total environment is completely virtual. Decreasing complexity increases the ability to control. If your classes and routines are not simple enough, that means they are not doing their jobs properly.

 

Please don’t forget to read my post about the new features of Java 10 JDK.

 

References

Related Post