De enkelvoudige verantwoordelijkheid (SRP), Open / Closed (OCP), Liskov-substitutie, interfacesegregatie en Afhankelijkheid inversie. Vijf behendige principes die je elke keer moeten begeleiden als je code schrijft.
Het zou onrechtvaardig zijn om u te vertellen dat elk van de SOLID-principes belangrijker is dan een ander. Waarschijnlijk heeft geen van de anderen een dergelijk onmiddellijk en diepgaand effect op uw code dan het principe van de afhankelijkheid van Inversie of kortweg DIP. Als je de andere principes moeilijk te begrijpen of toepassen vindt, begin dan met deze en pas de rest toe op code die al DIP respecteert.
A. Modulen op hoog niveau mogen niet afhankelijk zijn van modules op een laag niveau. Beide moeten afhangen van abstracties.
B. Abstracties mogen niet afhankelijk zijn van details. Details moeten afhangen van abstracties.
Dit principe werd gedefinieerd door Robert C. Martin in zijn boek Agile Software Development, Principles, Patterns and Practices en later opnieuw gepubliceerd in de C # -versie van het boek Agile Principles, Patterns and Practices in C #, en het is de laatste van de vijf SOLID-agile-principes.
Voordat we beginnen met coderen, wil ik je een verhaal vertellen. Bij Syneto waren we niet altijd zo voorzichtig met onze code. Een paar jaar geleden wisten we minder en hoewel we ons best deden, waren niet al onze projecten zo leuk. We gingen door de hel en weer terug en we leerden veel dingen met vallen en opstaan.
De SOLID-principes en de propere architectuurprincipes van oom Bob (Robert C. Martin) werden een spelwisselaar voor ons en transformeerden onze manier van coderen op manieren die moeilijk te beschrijven zijn. Ik zal in een notendop een aantal belangrijke architecturale beslissingen proberen op te stellen die zijn opgelegd door DIP en die een grote impact hebben gehad op onze projecten..
De meeste webprojecten bevatten drie hoofdtechnologieën: HTML, PHP en SQL. De specifieke versie van deze applicaties waar we het over hebben of welk type SQL-implementaties u gebruikt, is niet relevant. Het punt is dat informatie uit een HTML-formulier op de een of andere manier in de database terechtkomt. De lijm tussen de twee kan worden voorzien van PHP.
Wat essentieel is om hier afstand van te nemen, is dat hoe mooi de drie technologieën drie verschillende architecturale lagen vertegenwoordigen: gebruikersinterface, bedrijfslogica en persistentie. We zullen in één minuut de implicaties van deze lagen bespreken. Laten we ons voorlopig concentreren op enkele vreemde maar veel voorkomende oplossingen om de technologieën samen te laten werken.
Vaak heb ik projecten gezien die SQL-code in een PHP-tag in een HTML-bestand gebruikten, of PHP-code die pagina's en pagina's van echo's weergaf en rechtstreeks de $ _GET
of $ _POST
globale variabelen. Maar waarom is dit slecht?
De bovenstaande afbeeldingen vertegenwoordigen een onbewerkte versie van wat we in de vorige alinea hebben beschreven. De pijlen vertegenwoordigen verschillende afhankelijkheden, en zoals we kunnen concluderen, is in feite alles van alles afhankelijk. Als we een databasetabel moeten wijzigen, kunnen we uiteindelijk een HTML-bestand bewerken. Of als we een veld in HTML wijzigen, veranderen we mogelijk de naam van een kolom in een SQL-instructie. Of, als we naar het tweede schema kijken, moeten we misschien onze PHP aanpassen als de HTML verandert, of in zeer slechte gevallen, wanneer we alle HTML-inhoud vanuit een PHP-bestand genereren, zullen we zeker een PHP-bestand moeten wijzigen HTML-inhoud aanpassen. Het lijdt dus geen twijfel dat de afhankelijkheden zigzaggen tussen de klassen en modules. Maar hier eindigt het niet. U kunt procedures opslaan; PHP-code in SQL-tabellen.
In het bovenstaande schema retourneren query's naar de SQL-database PHP-code gegenereerd met gegevens uit de tabellen. Deze PHP-functies of klassen doen andere SQL-query's die verschillende PHP-code retourneren en de cyclus gaat door totdat uiteindelijk alle informatie is verkregen en geretourneerd ... waarschijnlijk naar de UI.
Ik weet dat dit voor veel van jullie misschien schandalig klinkt, maar als je nog niet hebt gewerkt met een project dat op deze manier is uitgevonden en geïmplementeerd, zul je dat zeker doen in je toekomstige carrière. De meeste bestaande projecten, ongeacht de gebruikte programmeertalen, zijn geschreven met oude principes in het achterhoofd, door programmeurs die het niet konden schelen of genoeg weten om het beter te doen. Als u deze zelfstudies leest, bent u waarschijnlijk een hoger niveau dan dat. Je bent klaar, of je klaarmaakt om je beroep te respecteren, om je ambacht te omhelzen en om het beter te doen.
De andere optie is om de fouten van je voorgangers te herhalen en te leven met de consequenties. Bij Syneto, nadat een van onze projecten een bijna onaantastbare staat bereikte vanwege zijn oude en kruisafhankelijke architectuur en we het eigenlijk voorgoed moesten verlaten, besloten we om nooit meer die weg terug te gaan. Sindsdien hebben we gestreefd naar een schone architectuur die de SOLID-principes correct respecteert, en het allerbelangrijkste is het principe van de afhankelijkheid van Inversie..
Wat zo verbazingwekkend is aan deze architectuur, is hoe de afhankelijkheden wijzen:
Het toepassen van het Dependency Inversion Principle (DIP) op architectonisch niveau is vrij eenvoudig als je de klassieke behendige ontwerppatronen respecteert. Het oefenen en illustreren binnen de bedrijfslogica is vrij eenvoudig en kan zelfs leuk zijn. We stellen ons een e-book reader-applicatie voor.
klasse Test breidt uit PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = nieuwe PDFBook (); $ r = nieuwe PDFReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); class PDFReader private $ book; functie __construct (PDFBook $ boek) $ this-> book = $ book; function read () return $ this-> book-> read (); class PDFBook function read () return "het lezen van een pdf-boek.";
We beginnen onze e-reader te ontwikkelen als een PDF-lezer. Tot zover goed. We hebben een PDF lezer
klasse met behulp van a PDFBook
. De lezen()
functie op de lezer afgevaardigden naar het boek lezen()
methode. We verifiëren dit door een regex-controle uit te voeren na een belangrijk deel van de string geretourneerd door PDFBook
's lezer()
methode.
Houd er rekening mee dat dit slechts een voorbeeld is. We zullen de leeslogica van PDF-bestanden of andere bestandsindelingen niet implementeren. Dat is de reden waarom onze tests eenvoudigweg controleren op enkele basisstrings. Als we de echte applicatie zouden schrijven, zou het enige verschil zijn hoe we de verschillende bestandsindelingen testen. De afhankelijkheidsstructuur lijkt erg op ons voorbeeld.
Het hebben van een PDF-lezer met een PDF-boek kan een goede oplossing zijn voor een beperkte applicatie. Als het de bedoeling was om een PDF-reader te schrijven en niets meer, zou het eigenlijk een acceptabele oplossing zijn. Maar we willen een generieke e-book reader schrijven, die verschillende formaten ondersteunt, waaronder onze eerste geïmplementeerde versie-PDF. Laten we onze lezersklasse hernoemen.
klasse Test breidt uit PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = nieuwe PDFBook (); $ r = nieuwe EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); class EBookReader private $ book; functie __construct (PDFBook $ boek) $ this-> book = $ book; function read () return $ this-> book-> read (); class PDFBook function read () return "het lezen van een pdf-boek.";
Hernoemen had geen functionele tegeneffecten. De tests zijn nog steeds voorbij.
Testen begon om 1:04 PM ...
PHPUnit 3.7.28 door Sebastian Bergmann.
Tijd: 13 ms, geheugen: 2,50 MB
OK (1 test, 1 bewering)
Het proces is voltooid met exitcode 0
Maar het heeft een serieus ontwerpeffect.
Onze lezer werd veel abstracter. Veel algemener. We hebben een generiek Ebook lezer
die een heel specifiek boektype gebruikt, PDFBook
. Een abstractie is afhankelijk van een detail. Het feit dat ons boek van het type PDF is, zou slechts een detail moeten zijn en niemand zou hiervan afhankelijk moeten zijn.
klasse Test breidt uit PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = nieuwe PDFBook (); $ r = nieuwe EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); interface EBook function read (); class EBookReader private $ book; function __construct (EBook $ book) $ this-> book = $ book; function read () return $ this-> book-> read (); class PDFBook implementeert EBook function read () return "leest een pdf-boek.";
De meest gebruikelijke en meest gebruikte oplossing om de afhankelijkheid om te keren, is om een meer abstracte module in ons ontwerp te introduceren. "Het meest abstracte element in OOP is een interface, dus elke andere klasse kan afhankelijk zijn van een interface en toch DIP respecteren".
We hebben een interface gemaakt voor onze lezer. De interface wordt gebeld EBook
en vertegenwoordigt de behoeften van de Ebook lezer
. Dit is een direct gevolg van het respecteren van het Interface Segregation Principle (ISP), dat het idee bevordert dat interfaces de behoeften van de klanten moeten weerspiegelen. Interfaces behoren tot de clients en daarom zijn ze benoemd om de typen en objecten weer te geven die de clients nodig hebben en ze bevatten methoden die de clients willen gebruiken. Het is alleen natuurlijk voor een Ebook lezer
gebruiken eBooks
en heb een lezen()
methode.
In plaats van één afhankelijkheid hebben we nu twee afhankelijkheden.
Ebook lezer
naar de EBook
interface en het is van het type gebruik. Ebook lezer
toepassingen eBooks
.PDFBook
naar hetzelfde toe EBook
interface maar het is van het type implementatie. EEN PDFBook
is gewoon een bepaalde vorm van EBook
, en implementeert die interface dus om te voldoen aan de behoeften van de klant.Het is niet verwonderlijk dat deze oplossing ons ook toestaat om verschillende soorten e-boeken aan te sluiten op onze reader. De enige voorwaarde voor al deze boeken is om te voldoen aan de EBook
interface en implementeren.
klasse Test breidt uit PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = nieuwe PDFBook (); $ r = nieuwe EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); function testItCanReadAMobiBook () $ b = nieuwe MobiBook (); $ r = nieuwe EBookReader ($ b); $ this-> assertRegExp ('/ mobi book /', $ r-> read ()); interface EBook function read (); class EBookReader private $ book; function __construct (EBook $ book) $ this-> book = $ book; function read () return $ this-> book-> read (); class PDFBook implementeert EBook function read () return "leest een pdf-boek."; klasse MobiBook implementeert EBook function read () return "leest een mobi-boek.";
Wat ons op zijn beurt naar het Open / Gesloten principe leidt, en de cirkel is gesloten.
Het principe van de afhankelijkheid van Inversie is er een die ons leidt of helpt om alle andere principes te respecteren. Het respecteren van DIP zal:
Dat is het. We zijn klaar. Alle tutorials over de SOLID-principes zijn voltooid. Voor mij persoonlijk was het ontdekken van deze principes en het uitvoeren van projecten met hen in gedachten een enorme verandering. Ik heb de manier waarop ik over design en architectuur denk helemaal veranderd en ik kan zeggen dat sindsdien alle projecten waar ik aan werk exponentieel eenvoudiger te beheren en te begrijpen zijn.
Ik beschouw de SOLID-principes als een van de meest essentiële concepten van objectgericht ontwerpen. Deze concepten die ons moeten helpen onze code beter te maken en ons leven als programmeurs veel gemakkelijker te maken. Goed ontworpen code is voor programmeurs gemakkelijker te begrijpen. Computers zijn slim, ze kunnen de code begrijpen, ongeacht de complexiteit ervan. Mensen hebben daarentegen een beperkt aantal dingen die ze kunnen bewaren in hun actieve, gerichte geest. Meer specifiek, het aantal van zulke dingen is The Magical Number Seven, Plus of Minus Two.
We moeten ernaar streven om onze code rond deze cijfers te laten structureren en er zijn verschillende technieken die ons daarbij helpen. Functies met een maximum van vier regels in lengte (vijf met de definitieregel inbegrepen), zodat ze allemaal tegelijk in onze geest kunnen passen. Inkepingen die niet vijf niveaus diep passeren. Klassen met niet meer dan negen methoden. Ontwerp patronen die meestal een aantal van vijf tot negen klassen gebruiken. Ons ontwerp op hoog niveau in de bovenstaande schema's gebruikt vier tot vijf concepten. Er zijn vijf SOLID-principes, die elk vijf tot negen subconcepten / modules / klassen moeten bevatten. De ideale grootte van een programmeerteam is tussen vijf en negen. Het ideale aantal teams in een bedrijf ligt tussen de vijf en negen.
Zoals je ziet, is het magische getal zeven, plus of min twee overal om ons heen, dus waarom zou jouw code anders zijn??