Det enda ansvaret (SRP), Open / Closed (OCP), Liskov-substitution, gränssnittssegregering och Dependensinversion. Fem smidiga principer som bör styra dig varje gång du skriver kod.
Det skulle vara orättvist att säga att någon av SOLID-principerna är viktigare än en annan. Förmodligen har ingen av de andra en sådan omedelbar och djupgående effekt på din kod än beroendeprincipen, eller DIP i korthet. Om du hittar de andra principerna svårt att förstå eller tillämpa, börja med den här och använd resten på kod som redan respekterar DIP.
A. Högnivåmoduler bör inte bero på lågnivåmoduler. Båda borde bero på abstraktioner.
B. Abstraktioner bör inte bero på detaljer. Detaljer bör bero på abstraktioner.
Denna princip definierades av Robert C. Martin i sin bok Agile Software Development, Principles, Patterns, and Practices och senare publicerades i C # versionen av boken Agile Principles, Patterns, and Practices i C #, och det är den sista av de fem SOLID agila principer.
Innan vi börjar kodning skulle jag vilja berätta en historia. På Syneto var vi inte alltid så försiktiga med vår kod. För några år sedan visste vi mindre och trots att vi försökte göra vårt bästa, var inte alla våra projekt så trevliga. Vi gick igenom helvetet och tillbaka igen och vi lärde oss många saker genom försök och fel.
SOLID-principerna och de rena arkitekturprinciperna för Uncle Bob (Robert C. Martin) blev en spelväxlare för oss och förvandlade vårt sätt att koda på sätt som är svåra att beskriva. Jag kommer att försöka exemplifiera, i ett nötskal, några viktiga arkitektoniska beslut som infördes av DIP, som hade stor inverkan på våra projekt.
De flesta webbprojekt innehåller tre huvudteknologier: HTML, PHP och SQL. Den speciella versionen av dessa applikationer vi pratar om eller vilken typ av SQL-implementeringar du använder är irrelevant. Saken är att informationen från en HTML-blankett måste hamna på ett eller annat sätt i databasen. Limet mellan de två kan förses med PHP.
Vad som är väsentligt att ta bort från detta är det hur trevligt de tre teknikerna representerar tre olika arkitektoniska lager: användargränssnitt, affärslogik och uthållighet. Vi kommer att prata om konsekvenserna av dessa lager på en minut. För nu låt oss fokusera på några udda men ofta uppkomna lösningar för att tekniken ska fungera tillsammans.
Många gånger har jag sett projekt som använde SQL-kod i en PHP-tagg inuti en HTML-fil, eller PHP-kod som ekkar sidor och sidor med HTML och tolkar direkt $ _GET
eller $ _POST
globala variabler. Men varför är det dåligt?
Bilderna ovan representerar en rå version av det vi beskrev i föregående stycke. Pilarna representerar olika beroenden, och som vi kan dra slutsatsen beror allting på allt. Om vi behöver ändra en databas tabell kan vi sluta redigera en HTML-fil. Eller om vi ändrar ett fält i HTML, kan vi sluta ändra namnet på en kolumn i ett SQL-uttalande. Eller om vi tittar på det andra schemat kan vi mycket väl behöva ändra vårt PHP om HTML ändras eller i mycket dåliga fall när vi genererar allt HTML-innehåll från en PHP-fil, så behöver vi säkert ändra en PHP-fil till Ändra HTML-innehåll. Så det finns ingen tvekan, beroendet är zigzagging mellan klasserna och modulerna. Men det slutar inte här. Du kan lagra procedurer; PHP-kod i SQL-tabeller.
I schemat ovan returnerar frågor till SQL-databasen PHP-kod som genereras med data från tabellerna. Dessa PHP-funktioner eller -klasser gör andra SQL-frågor som returnerar olika PHP-kod, och cykeln fortsätter tills äntligen all information erhålls och returneras ... förmodligen till gränssnittet.
Jag vet att det här kan vara skandalöst för många av er, men om du inte har arbetat med ett projekt som uppfunnits och implementerats på detta sätt kommer du säkert i din framtida karriär. De flesta befintliga projekt, oavsett vilka programmeringsspråk som användes, skrevs med gamla principer i åtanke, av programmerare som inte bryr sig eller vet tillräckligt för att göra bättre. Om du läser dessa handledningar är du troligtvis en nivå högre än den. Du är redo eller redo att respektera ditt yrke, att omfamna ditt hantverk och att göra det bättre.
Det andra alternativet är att upprepa de misstag som dina föregångare gjort och leva med konsekvenserna. Vid Syneto, efter att ett av våra projekt nådde ett nästan oförlåtligt tillstånd på grund av sin gamla och korsberoende arkitektur och vi var tvungna att överge det för alltid bestämde vi oss för att aldrig gå tillbaka ner den vägen igen. Sedan dess har vi strävat efter att ha en ren arkitektur som korrekt respekterar SOLID-principerna, och viktigast av principen om beroendeinversion.
Vad som är så fantastiskt om denna arkitektur är hur beroenden pekar:
Att tillämpa Dependency Inversion Principle (DIP) på arkitektonisk nivå är ganska lätt om du respekterar de klassiska smidiga designmönstren. Att utöva och exemplifiera det inom affärslogiken är ganska enkelt också och kan till och med vara roligt. Vi kommer att föreställa oss en e-bokläsareansökan.
klasstest utökar PHPUnit_Framework_TestCase funktion testItCanReadAPDFBook () $ b = ny PDFBook (); $ r = ny PDFReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); klass PDFReader privat $ bok; funktion __construct (PDFBook $ bok) $ this-> book = $ book; funktionsläsning () returnera $ this-> book-> read (); klass PDFBook funktion läs () returnera "läser en pdf-bok.";
Vi börjar utveckla vår e-läsare som PDF-läsare. Än så länge är allt bra. Vi har en PDFReader
klass med hjälp av a PDFBook
. De läsa()
funktionen på läsarnas delegater till bokens läsa()
metod. Vi bekräftar bara detta genom att göra en regexkontroll efter en nyckeldel av strängen som returneras av PDFBook
's läsare()
metod.
Tänk på att detta bara är ett exempel. Vi kommer inte att genomföra läslogiken för PDF-filer eller andra filformat. Därför kommer våra tester bara att leta efter några grundläggande strängar. Om vi skulle skriva den verkliga applikationen skulle den enda skillnaden vara hur vi testar de olika filformat. Beroende strukturen skulle mycket likna vårt exempel.
Att ha en PDF-läsare med en PDF-bok kan vara en ljudlösning för en begränsad applikation. Om vårt räckvidd var att skriva en PDF-läsare och ingenting mer, skulle det faktiskt vara en acceptabel lösning. Men vi vill skriva en generisk e-bokläsare, som stöder flera format, bland annat vår första implementerade version PDF. Låt oss byta namn på vår läsarklass.
klasstest utökar PHPUnit_Framework_TestCase funktion testItCanReadAPDFBook () $ b = ny PDFBook (); $ r = ny EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); klass EBookReader privat $ bok; funktion __construct (PDFBook $ bok) $ this-> book = $ book; funktionsläsning () returnera $ this-> book-> read (); klass PDFBook funktion läs () returnera "läser en pdf-bok.";
Renaming hade inga funktionella motverkaffekter. Testerna passerar fortfarande.
Testningen började klockan 13:04 ...
PHPUnit 3.7.28 av Sebastian Bergmann.
Tid: 13 ms, Minne: 2,50Mb
OK (1 test, 1 påstående)
Processen avslutad med utgående kod 0
Men det har en seriös designeffekt.
Vår läsare blev mycket mer abstrakt. Mycket mer generellt. Vi har en generisk Eboksläsare
som använder en mycket specifik bok typ, PDFBook
. En abstraktion beror på en detalj. Det faktum att vår bok är av typ PDF bör bara vara en detalj, och ingen ska bero på den.
klasstest utökar PHPUnit_Framework_TestCase funktion testItCanReadAPDFBook () $ b = ny PDFBook (); $ r = ny EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); gränssnittet EBook funktion läs (); klass EBookReader privat $ bok; funktion __construct (eBook $ book) $ this-> book = $ book; funktionsläsning () returnera $ this-> book-> read (); klass PDFBook implementerar EBook funktion läsning () return läser en pdf-bok. ";
Den vanligaste och mest använda lösningen för att invertera beroendet är att införa en mer abstrakt modul i vår design. "Det mest abstrakta elementet i OOP är ett gränssnitt. Således kan någon annan klass bero på ett gränssnitt och respekterar fortfarande DIP".
Vi skapade ett gränssnitt för vår läsare. Gränssnittet heter EBook
och representerar behoven hos Eboksläsare
. Detta är ett direkt resultat av att respektera gränssnittssegregationsprincipen (ISP) som främjar idén om att gränssnitt ska återspegla kundernas behov. Gränssnitt tillhör kunderna, och de namnges därför för att spegla de typer och objekt som kunderna behöver och de kommer att innehålla metoder som kunderna vill använda. Det är bara naturligt för en Eboksläsare
att använda e-böcker
och ha en läsa()
metod.
Istället för ett enda beroende har vi två beroenden nu.
Eboksläsare
mot EBook
gränssnittet och det är av typanvändning. Eboksläsare
användningar e-böcker
.PDFBook
mot samma EBook
gränssnitt men det är av typ implementering. en PDFBook
är bara en särskild form av EBook
, och implementerar sålunda det gränssnittet för att tillgodose kundens behov.Otroligt, med den här lösningen kan vi också plugga in olika typer av e-böcker i vår läsare. Det enda villkoret för alla dessa böcker är att tillfredsställa EBook
gränssnitt och implementera det.
klasstest utökar PHPUnit_Framework_TestCase funktion testItCanReadAPDFBook () $ b = ny PDFBook (); $ r = ny EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); funktion testItCanReadAMobiBook () $ b = nya MobiBook (); $ r = ny EBookReader ($ b); $ this-> assertRegExp ('/ mobi book /', $ r-> read ()); gränssnittet EBook funktion läs (); klass EBookReader privat $ bok; funktion __construct (eBook $ book) $ this-> book = $ book; funktionsläsning () returnera $ this-> book-> read (); klass PDFBook implementerar EBook funktion läsning () return läser en pdf-bok. "; klass MobiBook implementerar EBook funktion läsning () returnera "läser en mobibok.";
Som i sin tur leder oss till det öppna / stängda principen, och cirkeln är stängd.
Dependency Inversion Principle är en som leder eller hjälper oss att respektera alla andra principer. Respektera DIP kommer att:
Det är allt. Vi är klara. Alla handledning om SOLID-principerna är kompletta. För mig personligen upptäckte dessa principer och genomförande av projekt med dem i åtanke en enorm förändring. Jag har helt förändrat hur jag tänker på design och arkitektur och jag kan säga sedan dess är alla projekt jag arbetar på exponentiellt lättare att hantera och förstå.
Jag anser att SOLID-principerna är en av de viktigaste begreppen objektorienterad design. Dessa begrepp som måste vägleda oss för att göra vår kod bättre och vårt liv som programmerare mycket enklare. Väl utformad kod är lättare för programmerare att förstå. Datorer är smarta, de kan förstå kod oavsett komplexitet. Människor har å andra sidan ett begränsat antal saker de kan behålla i sitt aktiva, fokuserade sinne. Mer specifikt är antalet sådana saker The Magical Number Seven, Plus eller Minus Two.
Vi bör sträva efter att ha vår kod strukturerade runt dessa nummer och det finns flera tekniker som hjälper oss att göra det. Funktioner med högst fyra linjer i längd (fem med definitionslinjen medföljer) så att de alla kan passa på en gång i vårt sinne. Indryckningar som inte passerar fem nivåer djupt. Klasser med högst nio metoder. Designmönster som brukar använda ett antal fem till nio klasser. Vår högkvalitativa design i scheman ovan använder fyra till fem koncept. Det finns fem SOLID-principer, var och en kräver fem till nio delkoncept / moduler / klasser som exemplifieras. Den ideala storleken på ett programmeringslag är mellan fem och nio. Det ideala antalet lag i ett företag är mellan fem och nio.
Som du kan se är det magiska sju, plus eller minus två runt oss, så varför skulle din kod vara annorlunda?