Version without JavaScript, read-only: workshops, quizzes and running code are not available here. Full version · Catalogue · Sign in

Lecture notes · chapter 8

Coupling, cohesion, God Object

Coupling and cohesion, two criteria for assessing a design, and the God Object, which satisfies neither.

This chapter extends sequences 5 and 6.

Why architecture matters

A project is more than a set of classes that compile. Code is also expected to have the following qualities.

  • Maintainability: there will always be bugs, and clear code is easier to fix.
  • Reusability: a flight module should be usable elsewhere.
  • Scalability: when you are asked for the “CanardSolaire 3000”, you should not have to rework everything.

Coupling

Coupling measures how much two classes depend on each other. When it is strong, one cannot be modified without breaking the other. Chapter 5 covers the subject in detail; here is a summary.

class Dresseur {
    private CanardColvert canard;                 // tight coupling
    Dresseur(CanardColvert c) { this.canard = c; }
    void entrainer() { canard.voler(); }
}
class Dresseur {
    private ICanard canard;                       // loose coupling
    Dresseur(ICanard c) { this.canard = c; }
    void entrainer() { canard.voler(); }
}

Coupling can be spotted by looking at the import statements and the new expressions. A class that imports fifteen concrete types and instantiates six of them depends on twenty-one choices made elsewhere.

Cohesion

Cohesion measures the unity of responsibility of a class. A highly cohesive class has a single responsibility, and all its methods contribute to it.

class Canard {
    private String nom;

    public void voler()    { }
    public void nager()    { }
    public void chanter()  { }

    public void sauvegarderEnXML() { }     // persistence
    public void envoyerFacture()   { }     // invoicing
    public void calculerImpot()    { }     // accounting
}

This duck flies, swims, sings, saves itself as XML, sends invoices and calculates taxes. Such a class, which accumulates unrelated responsibilities, is called a God Object.

The solution is composition: each responsibility is placed in its own class, and the duck combines them.

class Canard {
    private String nom;
    private VolStrategy   vol;
    private ChantStrategy chant;

    public void effectuerVol()   { vol.voler(); }
    public void effectuerChant() { chant.chanter(); }
}

Persistence is moved into a DepotCanard, invoicing into a service. Each class can then be tested separately.

The SOLID principles

You saw these principles last year. Two of them are important for this module; the other three are simply to be known.

SRP: a single reason to change. The Canard class should be modified when a duck's behaviour changes, and not when the XML format changes. If there are two reasons to change, there should be two classes.

LSP: a subclass must be able to replace its parent class. The classic example does not involve a duck:

class Rectangle {
    protected int largeur, hauteur;
    void setLargeur(int l) { largeur = l; }
    void setHauteur(int h) { hauteur = h; }
}

class Carre extends Rectangle {
    @Override void setLargeur(int l) { largeur = l; hauteur = l; }
    @Override void setHauteur(int h) { largeur = h; hauteur = h; }
}

Code that sets the width to 5, then the height to 4, and expects an area of 20 gets 16. In geometry, a square is a rectangle, but it does not respect the contract of this Rectangle class. The LSP is violated: the inheritance is badly chosen, as in the cases studied in chapter 4.

O, I, D stand for the open/closed principle, interface segregation and dependency inversion. Chapter 5 applies two of them without naming them. They appear in the lecture notes but are not assessed in the exam.

KISS, DRY, YAGNI

KISS: Keep It Simple. Writing a CanardFactorySingletonManagerProxy to create a duck is excessive when new Canard("coincoin") is enough.

DRY: Don't Repeat Yourself. Copy-pasted code will have to be fixed in every place at the first bug.

void afficherCanard(Canard c)               { System.out.println(c.getNom() + " : coin !"); }
void afficherCanardColvert(CanardColvert c) { System.out.println(c.getNom() + " : coin !"); }
void afficherCanard(ICanard c) { System.out.println(c.getNom() + " : coin !"); }

YAGNI: You Aren't Gonna Need It. An interface added “just in case”, a factory for a single implementation or an abstraction layer that abstracts nothing add complexity without bringing anything.

Exam question

What distinguishes coupling from cohesion?

Coupling is a property that concerns several classes: it measures how much one depends on the other. Cohesion is a property of a single class: it measures how far its methods serve the same responsibility. The aim is loose coupling and strong cohesion.