OOP: Design Principles (deel 2)

July 22nd, 2012 | 6 min read | Coding rules, Object oriënted, PHP

In dit tweede deel over design principles ga ik het hebben over loose coupling en high cohesion en code re-usability.

Heads up!

Deze tutorial over de design principles is het tweede deel en sluit aan op Design Principles (deel 1).

Verantwoordelijkheden

Om ervoor te zorgen dat je classes leesbaar blijven moet je ervoor zorgen dat methodes niet teveel verantwoordelijkheden krijgen en dus niet enkele honderden lijnen groot zijn. In principe mag elke methode maar juist één taak volbrengen. Dat wilt dus zeggen dat er geen methodes zoals

TelOpEnVermenigvuldigDaarnaMetHetTegengesteldeVanHetOmgekeerdeVanHetGetal

horen te zijn.
Als een methode meer dan 20 lijnen code begint te tellen, dan begint het stilletjes aan echt de moeite te worden om je methodes op te splitsen.

Een goede tip die je kan gebruiken is ervoor te zorgen dat je geen comments in je methodes schrijft (enkel boven je signatuur). Vanaf dat je comments begint te schrijven in je methode, dan wilt dat meestal zeggen dat je je methode in meerdere logische delen kan opdelen en dus ook in meerdere methodes zou kunnen opdelen.
Een voorbeeld:

<?php

class OudeClass {

        public function GetAantalOuderDan30() {
                // Initializeeer leeftijden
                $leeftijden = array(10, 13, 49, 30, 20, 47, 53, 7, 72, 29, 63);

                // Tel ouder dan 30
                $teller = 0;
                foreach ($leeftijden as $leeftijd) {

                        // Controle ouder dan 30
                        if ($leeftijd > 30) {
                                $teller++;
                        }
                }

                return $teller;
        }
}

class NieuweClass {

        public function GetLeeftijden() {
                return array(10, 13, 49, 30, 20, 47, 53, 7, 72, 29, 63);
        }

        public function TelOuderDan30($leeftijden) {
                $teller = 0;
                foreach ($leeftijden as $leeftijd) {
                        $teller += $this->IsOuderDan30($leeftijd);
                }
                return $teller;
        }

        public function IsOuderDan30($leeftijd) {
                return ($leeftijd > 30);
        }

        public function GetAantalOuderDan30() {
                return $this->TelOuderDan30($this->GetLeeftijden());
        }
}
$oudeClass = new OudeClass();
$nieuweClass = new NieuweClass();

echo $oudeClass->GetAantalOuderDan30() ." ". $nieuweClass->GetAantalOuderDan30();

?>

In

NieuweClass

zie je duidelijk dat alles opgesplitst is, in dit geval mag het misschien een beetje op overkill lijken, maar ik denk dat hiermee wel duidelijk is wat ik bedoel.
Het handige hieraan is dat methode per methode alles duidelijk wordt en dat de methodes niet gigantisch groot worden.

Ook code re-usability kan je hiermee verbeteren, want nu kan je elk stuk afzonderlijk aanroepen en dus ook gebruiken in andere methodes. Stel je maar eens voor dat we van een andere lijst van leeftijden willen tellen wie er ouder is dan 30, dan kunnen we eigenlijk een groot deel herbruiken zonder veel code aan te passen.

Loose coupling, High cohesion

Loose coupling en high cohesion zijn termen die je heel vaak zal zien als je eender welk OO handboek open gooit en leest. De bedoeling is om te streven naar code met een zo’n laag mogelijke koppeling, maar een zo hoog mogelijke cohesie, wat dit juist inhoudt, daar zal je nu achter komen.

Loose coupling

Loose coupling wilt zeggen dat je zo weinig mogelijk dependencies tussen je classes wilt hebben. Stel dat je een class A en B hebt en dat je in elke methode van class A wel 3 verschillende methodes van class B oproept en dat voor 10 methodes ofzo, dan kan je spreken van een hoge koppeling.
Stel dat je nu een aanpassing wilt maken aan class B, dan moet je al die wijzigingen ook doorvoeren in class A. Een ander nadeel van zo’n hoge koppeling is dat het de leesbaarheid van je code echt drastisch vermindert. Je krijgt namelijk niet direct een idee van wat een methode inhoudt, want dan moet je eerst naar al die methodes in class B gaan kijken en begrijpen.

Kortom, loose coupling verhoogt de leesbaarheid en de aanpasbaarheid van je code, wat zeer belangrijk is als je het eerste design principe (change) in je achterhoofd houdt.

High cohesion

High cohesion sluit aan bij het design principe over verantwoordelijkheden. Elke methode mag maar juist één verantwoordelijkheid hebben, maar dit wilt ook zeggen dat al deze methodes nauw samenwerken met elkaar. Deze samenhang van methodes noemen we de cohesie van de methodes.
Hoe hoger de cohesie, des te eenvoudiger het te begrijpen is (stel je voor dat de methode IsOuderDan30 uit het voorgaande voorbeeld ergens in een totaal andere class zou staan, dan moeten we kennis hebben van deze 2 classes. Willen we dan op een bepaald moment een wijziging aanbrengen, dan zal je hoogst waarschijnlijk in beide classes wijzigingen moeten aanbrengen.

Code re-usability

Code re-usability of het herbruiken van code is iets wat je kan bereiken door het vorige design principe (loose coupling, high cohesion) goed op te volgen. Als je zoveel mogelijk methodes met één enkele verantwoordelijkheid hebt en je hebt deze gegroepeerd in classes, dan zal je merken dat het eenvoudig wordt om een nieuwe class te schrijven die van deze methodes gebruik gaat maken zonder instanties van honderden classes te moeten maken en zelf nog veel code te moeten schrijven.
Vanaf het moment dat je vergelijkbare of gewoonweg dezelfde code schrijft als je eerder al eens hebt geschreven, dan moet je gaan nadenken om deze functionaliteit af te splitsen zodanig dat ze in beide gevallen gebruikt kan worden.

Een voorbeeldje zou zijn dat als we nu alle personen willen vinden die ouder dan 40 zijn, dat we dan de methodes

TelOuderDan30

en

IsOuderDan30

gaan kopiëren en hiervan de methodes

TelOuderDan40

en

IsOuderDan40

gaan maken. Ik denk dat het voor zich spreekt dat dit een slechte aanpak is omdat je zo maar kan blijven doorgaan en enorm veel code gaat herschrijven in plaats van herbruiken.
Een beter design zou het volgende zijn:

<?php

class NieuweClass {

        public function GetLeeftijden() {
                return array(10, 13, 49, 30, 20, 47, 53, 7, 72, 29, 63);
        }

        public function TelOuderDan($leeftijden, $ouderDan) {
                $teller = 0;
                foreach ($leeftijden as $leeftijd) {
                        $teller += $this->IsOuderDan($leeftijd, $ouderDan);
                }
                return $teller;
        }

        public function IsOuderDan($leeftijd, $ouderDan) {
                return ($leeftijd > $ouderDan);
        }

        public function GetAantalOuderDan30() {
                return $this->TelOuderDan($this->GetLeeftijden(), 30);
        }

        public function GetAantalOuderDan40() {
                return $this->TelOuderDan($this->GetLeeftijden(), 40);
        }
}

$nieuweClass = new NieuweClass();

echo $nieuweClass->GetAantalOuderDan30() ." ". $nieuweClass->GetAantalOuderDan40();

?>

We hebben nu wel de methode

TelOuderDan30

en

IsOuderDan30

aangepast (gegeneraliseerd), maar nu kunnen we met gemak een methode

GetAantalOuderDan50

gaan maken en nog 10 andere vergelijkbare methodes.

Back to tutorialsContact me on TwitterDiscuss on Twitter

Profile picture

Dimitri "g00glen00b" Mestdagh is a consultant at Cronos and tech lead at Aquafin. Usually you can find him trying out new libraries and technologies. Loves both Java and JavaScript.