Ga naar inhoud
Log in om dit te volgen  
kingunit

Design Patterns: SOLID

Aanbevolen berichten

Design Patterns?
Dit is niet gerelateerd aan UX design of iets grafisch, dit gaat over patronen in software design. Design patterns of ontwerppatronen heeft eigenlijk alles te maken met software architectuur en structuur. Als beginnende programmeur schrijf je vaak procedural code dit is het tegenovergestelde van ?objectgeoriënteerd programmeren. Weet je nog niks over objectgeoriënteerd programmeren? Dan raad ik je aan om je daar eerst in te verdiepen voor het lezen van dit stuk.

Zoals ik beschreef zijn design patterns ontwerppatronen binnen het programmeren. Voor jouw development team betekent dat:

  • Het is makkelijker om je code / software te onderhouden.
  • Door ?abstracties is jouw code beter te lezen.
  • Veel hergebruik van je eigen code.
  • Het is voor een nieuwe programmeur in jouw team makkelijker om gelijk mee te draaien.
  • Eventuele voorbeelden schrijf ik in PHP. Echter kun je design patterns overal toepassen waar je classes, abstracties en interfaces kunt gebruiken.

Wat is SOLID?
SOLID is een afkorting van een groep design patterns. De principes waar ik het nu over ga hebben zijn:
  • S - Single Responsibility
  • O - Open/Closed
  • L - Liskov Substitution
  • I - Interface Segregation
  • D - Dependency Inversion

Waarschijnlijk klinken de termen nog een beetje ingewikkeld maar ik hoop dat je na het lezen van mijn post er wat meer over weet.


S - Single Responsibility
Dit is de eerste design pattern waar ik het over gaan hebben. Persoonlijk vind ik dit ?é?én van de makkelijkste patronen om te begrijpen. De naam single responsibility zegt het eigenlijk al. Één verantwoordelijkheid. Hieronder heb ik even een voorbeeld binnen PHP waar het patroon niet wordt gehanteerd.

Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
<?php

class Gebruiker {
    public function aanmaken(array $data) {
        // Sla de gebruiker op in de database
    }

    public function logFoutmelding(string $data) {
        // Schrijf een foutmelding naar een log bestand.
    }
}


Zoals je ziet heeft de Gebruiker klasse meerdere verantwoordingen. Het is zijn verantwoording om een nieuwe gebruiker aan te maken en het is ook zijn verantwoording om een foutmelding naar een log bestand te schrijven terwijl dat helemaal niet gerelateerd is aan een gebruiker. Het is dus de bedoeling dat je deze methodes eruit sloopt en ze een plekje geeft in zijn eigen klasse. Hieronder een voorbeeld hoe het wel zou moeten.

Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class Gebruiker {
    public function aanmaken(array $data) {
        // Sla de gebruiker op in de database
    }
}


class Logger {
    public function logNaarBestand(string $data) {
        // Schrijf een foutmelding naar een log bestand.
    }
}


$gebruiker = new Gebruiker();
$gebruiker->aanmaken([gebruikersnaam, wachtwoord]);

$logger = new Logger();
$logger->logNaarBestand(Dit is een random log.);




O - Open/Closed
Open/Closed staat ook wel voor ?open for extension and closed for modification. Vertaald naar het Nederlands: open voor uitbreiding, gesloten voor verandering. Hierbij geld de regel dat je de classes zo schrijft dat je de broncode niet hoeft aan te passen. Als jij een lopende applicatie/website hebt draaien en je past de broncode van je applicatie aan zou je eigenlijk een groot deel van je website weer langs moeten gaan om te kijken of alles nog steeds werkt zoals het hoort. Dit patroon gaat jou hierbij helpen. Misschien klinkt dit een beetje gek maar hieronder weer een stukje code waarbij ik het patroon verbreek. Dit is dus de onjuiste methode.

??
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

class BetaalManager {
    protected $ideal;

    public function __construct(Ideal $ideal) {
        $this->ideal = $ideal;
    }


    public function afhandelen() {
        $this->ideal->betaal();
    }
}


class iDeal {
    public function betaal() {
        // Verwerk een iDeal betaling via een API of whatever.
    }
}


// We maken een nieuw iDeal object.
$ideal = new Ideal();

// We injecteren het object in onze BetaalManager
$betaalManager = new BetaalManager($ideal);

// We handelen de transacties af. (in dit geval onze iDeal transactie)
$betaalManager->afhandelen();



Als je kijkt naar de BetaalManager klasse, daar breken we nu eigenlijk ons patroon. Wat nou als je een wijziging wilt doorvoeren? Er komt bijvoorbeeld een nieuwe betaalmethode, PayPal als voorbeeld. De huidige klasse moet dus worden gewijzigd en deze kan dus niet worden hergebruikt. (Hey, zei ik in de inleiding nou dat we juist code willen herbruiken?). Allereerst gaan we een nieuwe interface aanmaken. Elk betaalmethode gaat onze nieuwe BetaalmethodeInterface implementeren.

??
Codeblok (voorbeeld.php):
1
2
3
4
5
<?php

interface BetaalmethodeInterface {
    public function betaal();
}


Als je nog niet weet wat een interface is raad ik je aan om je daar even in te gaan verdiepen. Nu gaan we onze BetaalManager klasse aanpassen en gebruik maken van de interface.

??
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

class BetaalManager {
    protected $betaalmethode;

    public function __construct(BetaalmethodeInterface $betaalmethode) {
        $this->betaalmethode = $betaalmethode;
    }


    public function afhandelen() {
        $this->betaalmethode->betaal();
    }
}


We vervangen eigenlijk alles wat gekoppeld was aan iDeal met onze interface. Omdat wij onze interface hanteren weten we altijd dat onze betaalmethode de afhandelen methode heeft. Wat we nu nog even moeten doen is onze interface implementeren in de iDeal klasse.

Codeblok (voorbeeld.php):
1
2
3
4
5
6
7

<?php
class iDeal implements BetaalmethodeInterface{
    public function betaal() {
        // Verwerk een iDeal betaling via een API of whatever.
    }
}


Omdat we op deze manier programmeren is het heel makkelijk om nieuwe betaalmethodes toe te voegen zonder de BetaalManager te wijzigen. Je maakt een PayPal klasse aan die de BetaalmethodeInterface implementeert waardoor het weer verplicht is om een betaal methode te hebben.

?
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class iDeal implements BetaalmethodeInterface{
    public function betaal() {
        // Verwerk een iDeal betaling via een API of whatever.
    }
}


class PayPal implements BetaalmethodeInterface{
    public function betaal() {
        // Verwerk een iDeal betaling via een API of whatever.
    }
}


// iDeal transactie
$ideal = new Ideal();
$betaalManager = new BetaalManager($ideal);
$betaalManager->betaal();

// iDeal transactie
$paypal = new PayPal();

$betaalManager = new BetaalManager($paypal);



?L - Liskov Substitution
De ??substitutieprincipe van Liskov heeft te maken met het overerven van klasses. Hierdoor verhoog je het hergebruik van jouw eigen code als je dit patroon volgt. Op internet wordt er vaak een voorbeeld laten zien van voertuigen dus ik ga ook een voertuig voorbeeld laten zien. Dit is een fout die veel beginnende programmeurs maken. (natuurlijk niet met een voertuig klasse).

??
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

class Auto {
    public function gas() {
        echo Vroem vroem!;
    }
}


class Motor {
    public function gas() {
        echo Vroem vroem!;
    }
}


class Motorfiets {
    public function gas() {
        echo Vroem vroem!;
    }
}


class Vrachtwagen {
    public function gas() {
        echo Vroem vroem!;
    }
}


$voertuig = new Auto();
$voertuig->gas();


Je ziet dat er telkens een gas methode is en die doet telkens exact hetzelfde. Willen wij de logica vervangen (Vroem vroem) moeten wij dus alle methodes langs en aanpassingen doorvoeren. Dit willen we juist niet doen want hierdoor moet de applicatie weer grondig worden getest etc. Hier een voorbeeld hoe je het bijvoorbeeld wel zou kunnen doen.

?
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

class Voertuig {
    public function gas() {
        echo Vroem vroem!;
    }
}

class Auto extends Voertuig {
}


class Motor extends Voertuig {
}


class Motorfiets extends Voertuig {
}


class Vrachtwagen extends Voertuig {
}


$voertuig = new Vrachtwagen();

$voertuig->gas();


In dit voorbeeld zie je dat we gebruik maken van overerving (extends). We zorgen ervoor dat elk voertuig alle functies van onze Voertuig klasse overeft. Hierdoor hoeven we dus maar 1x onze logica te schrijven i.p.v. 4 keer. Dit is ook weer een stukje code hergebruiken.


?I - Interface Segregation
De I van SOLID staat voor Interface Segregation. Klinkt allemaal heel erg ingewikkeld maar het valt gelukkig mee. Je kan dit patroon een beetje vergelijken met het vorige patroon (Liskov). Dit patroon is niks meer dan: implementeer alleen een interface als de klasse alle methodes gaat gebruiken. Laten we maar eerst een probleem maken:

?
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

interface VogelInterface {
    public function vlieg();
}


class Kraai implements VogelInterface {
    public function vlieg() {
        // I believe I can fly ;)
    }
}


class Pinguing implements VogelInterface {
    public function vlieg() {
        // Maar oh wacht ...
    }

}


We hebben een VogelInterface aangemaakt, hierdoor is elke klasse die onze interface implementeerd verplicht om de vlieg methode te gebruiken. Voor de meeste vogels is dit prima; een kraai vliegt, een papegaai vliegt. Maar wacht eens ... een pinguing is daadwerkelijk een vogel maar een pinguing kan niet vliegen. Hoe lossen we dit op? Nou simpelweg niet de VogelInterface implementeren.

??
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

interface VogelInterface {
    public function vlieg();
}


class Kraai implements VogelInterface {
    public function vlieg() {
        // I believe I can fly ;)
    }
}


class Pinguing  {
    public function loop() {
        // De pinguing begint met lopen.
    }
}



Het klinkt allemaal een beetje logisch maar met dit patroon is het de bedoeling dat je alleen een interface implementeert als alle methodes verplicht zijn.


?D - Dependency Inversion
Dit is het laatste patroon van SOLID. Als je gebruikt maakt van dependency inversion kun je ook weer code gaan hergebruiken. Hierbij wordt de term ?loosely coupled vaak gebruikt. Het is dus de bedoeling dat klasses wel met elkaar communiceren maar ze weten verder niks van elkaar. De relatie tussen klasses zijn dus niet hard coded. Omdat ik dit persoonlijk zelf heel moeilijk vind om het duidelijk uit te leggen heb ik een stukje tekst op internet gevonden met een real life voorbeeld: ??

Een voorbeeld van loosly coupled, buiten de computerindustrie, is het verbinden van een (snoer)schakelaar en een lamp. Om het licht aan en uit te kunnen doen, is het noodzakelijk de schakelaar en de lamp te verbinden. In praktijk doet iedereen dat met een stukje snoer. Essentieel hierbij is dat zowel de lamp als de schakelaar eenzelfde (standaard) koppeling hebben: het kroonsteentje. Met een eenvoudige handeling (het vastdraaien van een schroef) komt de verbinding tot stand zonder dat de lamp of de schakelaar veranderd of aangepast hoeft te worden. De koppeling is losjes: noch de lamp, noch de schakelaar zijn speciaal ontworpen om met de ander gebruikt te worden. Ook een andere lamp (van een ander merk of type), meerdere lampen of een of meerdere (andere) schakelaars kunnen gebruikt worden. Vergelijk dat met een staande lamp met een ingebouwde schakelaar: ook die bedient de lamp maar de koppeling is hard. Het is nauwelijks mogelijk om een andere schakelaar te gebruiken.

We gaan dit eens omzetten naar PHP code.

??
Codeblok (voorbeeld.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

interface AanUitInterface {
    public function aan();
    public function uit();
}


class Lamp {
    public function __construct() {
        $this->isAan = false;
    }


    public function aan() {
        $this->isAan = true;
        echo Het licht gaat aan!;
    }


    public function uit() {
        $this->isAan = false;
        echo Het licht gaat uit!;
    }
}



class Schakelaar {
    public function __construct($outlet) {
        $this->outlet = $outlet;
    }


    public function omhoog() {
        $this->outlet->aan();
    }


    public function omlaag() {
        $this->outlet->uit();
    }
}


$lamp = new Lamp();
$schakelaar = new Schakelaar($lamp);

$schakelaar->omhoog();
$schakelaar->omlaag();


We maken dus eerst een interface aan. Elke apparaat wat dus aan en uit kan implementeert onze AanUitInterface. Vervolgens maken we een Lamp klasse aan, deze implementeert onze AanUitInterface en dat betekent dat hij zowel de aan en de uit methode moet hebben. Hierbij maken we even een simpele echo. Vervolgens maken we een schakelaar klasse aan, deze ontvangt in de constructor ons object (hij weet niet of het een lamp of wat anders is, loosly coupled...). Vervolgens roept hij op dat object een aan() methode. Omdat het object onze AanUitInterface hanteert weten we dus dat hij sowieso een aan() methode heeft.

Vervolgens maken we een nieuw Lamp object aan en injecteren wij dit object in onze schakelaar. Via de schakelaar roepen wij de omhoog methode aan en hierdoor gaat het licht aan of uit. Het mooie hiervan, is dat je nu heel gemakkelijk een nieuw apparaat kan toevoegen en deze kunt injecteren in de schakelaar zonder hiervoor de code van de schakelaar te wijzigen.


Tot slot
Nou dit is het einde van mijn eerste design patterns tutorial. Ik heb dit voornamelijk geschreven omdat ik graag mezelf wil verbeten in het uitlegen van dingen naar andere. Voorbeelden en manieren kunnen vast beter en/of op een andere manier. Binnen OOP/Design patterns heeft iedereen weer zijn eigen methode, dit is gewoon de manier hoe ik het zie/gebruik. Mocht er interesse zijn wil ik meer tutorials schrijven over design patterns. En mocht er nog vragen zijn of opmerkingen dan hoor ik dat graag.

Deel dit bericht


Link naar bericht
Delen op andere sites
Cool dat er interesse is in meer. Ik zal kijken waar ik het volgend artikel over ga schrijven. Als er mensen iets specifieks willen lezen over structuur of bepaalde systemen. Laat het me even weten.

Deel dit bericht


Link naar bericht
Delen op andere sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Reageer op dit topic...

×   Geplakt als RTF formaat tekst.   Plak in plaats daarvan als platte tekst

  Er zijn maximaal 75 emoji toegestaan.

×   Je link werd automatisch ingevoegd.   Tonen als normale link

×   Je vorige inhoud werd hersteld.   Leeg de tekstverwerker

×   Je kunt afbeeldingen niet direct plakken. Upload of voeg afbeeldingen vanaf een URL in

Log in om dit te volgen  

×
×
  • Nieuwe aanmaken...