Enkelt ansvar (SRP), Öppna / Stäng, Liskovs Substitution, Interface Segregation, och Dependency Inversion. Fem smidiga principer som bör styra dig varje gång du skriver kod.
En klass bör bara ha en anledning att ändra.
Definierad av Robert C. Martin i sin bok Agile Software Development, Principles, Patterns, and Practices och senare publiceras i C # versionen av boken Agile Principles, Patterns and Practices i C #, är det en av de fem SOLID agile principerna. Vad det säger är väldigt enkelt, men att uppnå denna enkelhet kan vara väldigt knepig. En klass bör bara ha en anledning att ändra.
Men varför? Varför är det så viktigt att bara ha en orsak till förändring?
I statiskt skrivna och sammanställda språk kan flera orsaker leda till flera oönskade omfördelningar. Om det finns två olika skäl att ändra, är det tänkbart att två olika grupper kan arbeta på samma kod av två olika skäl. Var och en kommer att behöva distribuera sin lösning, som vid ett sammanställt språk (som C ++, C # eller Java) kan leda till inkompatibla moduler med andra lag eller andra delar av applikationen.
Även om du kanske inte använder ett sammanställt språk, kan du behöva retestera samma klass eller modul av olika skäl. Det betyder mer QA-arbete, tid och ansträngning.
Att bestämma det enda ansvar som en klass eller modul ska ha är mycket mer komplex än att bara titta på en checklista. Ett ledtråd för att hitta våra skäl till förändring är till exempel att analysera publiken för vår klass. Användarna av programmet eller systemet vi utvecklar som serveras av en viss modul kommer att vara de som begär ändringar i den. De som serveras kommer att be om förändring. Här är några moduler och deras möjliga publik.
Att förena konkreta personer med alla dessa roller kan vara svårt. I ett litet företag kan en enda person behöva tillfredsställa flera roller, medan i ett stort företag kan det finnas flera personer som tilldelas en enskild roll. Så det verkar mycket mer rimligt att tänka på rollerna. Men roller i sig är ganska svåra att definiera. Vad är en roll? Hur hittar vi det? Det är mycket lättare att föreställa sig aktörer som gör dessa roller och associerar vår publik med dessa aktörer.
Så om vår publik definierar orsakerna till förändring definierar aktörerna publiken. Detta hjälper oss kraftigt att minska begreppet konkreta personer som "John the architect" till arkitektur eller "Mary referent" till Operations.
Så ett ansvar är en familj av funktioner som tjänar en viss skådespelare. (Robert C. Martin)
I betydelsen av denna resonemang blir skådespelare en källa till förändring för familjen av funktioner som tjänar dem. Eftersom deras behov förändras, måste den specifika familjen funktioner också förändras för att tillgodose deras behov.
En skådespelare för ansvar är den enda källan till förändring för det ansvaret. (Robert C. Martin)
Låt oss säga att vi har en bok
klass inkapslar begreppet en bok och dess funktioner.
klassbok funktion getTitle () returnera "En stor bok"; funktion getAuthor () returnera "John Doe"; funktion turnPage () // pekare till nästa sida funktion printCurrentPage () echo "aktuellt sidinnehåll";
Det kan se ut som en rimlig klass. Vi har bok, den kan ge sin titel, författare och det kan vända sidan. Slutligen kan den också skriva ut den aktuella sidan på skärmen. Men det finns ett litet problem. Om vi tänker på aktörerna som är involverade i driften av bok
objekt, vem kan de vara? Vi kan lätt tänka på två olika aktörer här: Bokhantering (som bibliotekarie) och Data Presentation Mechanism (som hur vi vill leverera innehållet till användaren - på skärmen, grafiskt användargränssnitt, text-bara användargränssnitt, kanske utskrift) . Det här är två väldigt olika aktörer.
Att blanda affärslogik med presentation är dålig eftersom det strider mot principen om ensam ansvar (SRP). Ta en titt på följande kod:
klassbok funktion getTitle () returnera "En stor bok"; funktion getAuthor () returnera "John Doe"; funktion turnPage () // pekare till nästa sida funktion getCurrentPage () returnera "aktuellt sidinnehåll"; gränssnitt Skrivare funktion printPage ($ page); klass PlainTextPrinter implementerar skrivare funktion printPage ($ page) echo $ page; klass HtmlPrinter implementerar skrivare funktion printPage ($ page) echo ''. $ sida. '';
Även detta mycket grundläggande exempel visar hur separering av presentationen från affärslogiken, och respekterar SRP, ger stora fördelar i vår designs flexibilitet.
Ett liknande exempel på det ovanstående är när ett objekt kan spara och hämta sig från presentationen.
klassbok funktion getTitle () returnera "En stor bok"; funktion getAuthor () returnera "John Doe"; funktion turnPage () // pekare till nästa sida funktion getCurrentPage () returnera "aktuellt sidinnehåll"; funktionen spara () $ filename = '/ documents /'. $ This-> getTitle (). '-'. $ This-> getAuthor (); file_put_contents ($ filnamn, serialize ($ this));
Vi kan igen identifiera flera aktörer som Book Management System och Persistence. När vi vill förändra uthållighet, behöver vi ändra den här klassen. När vi vill ändra hur vi kommer från en sida till nästa måste vi ändra den här klassen. Det finns flera förändringsaxlar här.
klassbok funktion getTitle () returnera "En stor bok"; funktion getAuthor () returnera "John Doe"; funktion turnPage () // pekare till nästa sida funktion getCurrentPage () returnera "aktuellt sidinnehåll"; klass SimpleFilePersistence funktionen spara (bok $ bok) $ filename = '/ documents /'. $ book-> getTitle (). '-'. $ Bok-> getAuthor (); file_put_contents ($ filnamn, serialize ($ bok));
Att flytta persistensoperationen till en annan klass skiljer tydligt ansvaret och vi kommer att vara fria att utbyta persistensmetoder utan att påverka vår bok
klass. Till exempel implementera a DatabasePersistence
klassen skulle vara trivial och vår affärslogik byggd runt verksamheten med böcker kommer inte att förändras.
I mina tidigare artiklar nämnde jag ofta och presenterade det höga arkitektoniska schemat som kan ses nedan.
Om vi analyserar det här schemat kan du se hur principen om ensam ansvar respekteras. Objektskapande är åtskild till höger i fabrikerna och huvudpunkten för vår ansökan, en aktör ett ansvar. Persistens tas också hand om i botten. En separat modul för separat ansvar. Slutligen till vänster har vi presentationen eller leveransmekanismen om du vill, i form av en MVC eller någon annan typ av användargränssnitt. SRP respekteras igen. Allt som återstår är att ta reda på vad man ska göra inne i vår affärslogik.
När vi tänker på programvaran som vi behöver skriva kan vi analysera många olika aspekter. Till exempel kan flera krav som påverkar samma klass representera en förändringsaxel. Dessa förändringsaxlar kan vara en ledtråd för ett enda ansvar. Det finns stor sannolikhet att grupper av krav som påverkar samma grupp av funktioner kommer att ha skäl att ändra eller specificeras i första hand.
Det primära värdet av programvara är lätt att byta. Sekundären är funktionalitet, i den meningen att den uppfyller så mycket krav som möjligt, och tillgodoser användarens behov. För att uppnå ett hög sekundärt värde är dock ett primärt värde obligatoriskt. För att hålla vårt primära värde högt måste vi ha en design som är lätt att ändra, utvidga, anpassa nya funktioner och se till att SRP respekteras.
Vi kan skälet i steg för steg:
Så när vi utformar vår programvara borde vi:
klassbok funktion getTitle () returnera "En stor bok"; funktion getAuthor () returnera "John Doe"; funktion turnPage () // pekare till nästa sida funktion getCurrentPage () returnera "aktuellt sidinnehåll"; funktion getLocation () // returnerar positionen i biblioteket // dvs. hyllnummer och rumsnummer
Nu kan det tyckas helt rimligt. Vi har ingen metod som handlar om uthållighet eller presentation. Vi har vårt turnPage ()
funktionalitet och några metoder för att ge olika uppgifter om boken. Vi kan dock ha problem. För att få reda på, kanske vi vill analysera vår applikation. Funktionen getLocation ()
kan vara problemet.
Alla metoder av bok
klassen handlar om affärslogik. Så vårt perspektiv måste vara från företagets synvinkel. Om vår ansökan skrivs för att användas av reella bibliotekarier som söker böcker och ger oss en fysisk bok, kan SRP bli kränkt.
Vi kan anse att skådespelarnas verksamhet är de som är intresserade av metoderna getTitle ()
, getAuthor ()
och getLocation ()
. Klienterna kan också ha tillgång till ansökan för att välja en bok och läsa de första sidorna för att få en uppfattning om boken och bestämma om de vill ha det eller inte. Så skådespelarnas läsare kan vara intresserade av alla metoder utom getLocations ()
. En vanlig klient bryr sig inte om var boken hålls i biblioteket. Boken kommer att överlämnas till kunden av bibliotekarie. Så, vi har faktiskt en kränkning av SRP.
klassbok funktion getTitle () returnera "En stor bok"; funktion getAuthor () returnera "John Doe"; funktion turnPage () // pekare till nästa sida funktion getCurrentPage () returnera "aktuellt sidinnehåll"; klass BookLocator funktionsplats (bok $ bok) // returnerar positionen i biblioteket // dvs. hyllnummer och rumsnummer $ libraryMap-> findBookBy ($ book-> getTitle (), $ book-> getAuthor ());
Presentera BookLocator
, Bibliotekaren kommer att vara intresserad av BookLocator
. Klienten kommer att vara intresserad av bok
endast. Det finns givetvis flera sätt att implementera en BookLocator
. Den kan använda författaren och titeln eller ett bokobjekt och få den information som krävs från bok
. Det beror alltid på vår verksamhet. Vad som är viktigt är att om biblioteket ändras och bibliotekarie måste hitta böcker i ett annorlunda organiserat bibliotek, bok
objektet påverkas inte. På samma sätt, om vi bestämmer oss för att tillhandahålla en sammanställd sammanfattning till läsarna istället för att låta dem bläddra igenom sidorna, så påverkar inte bibliotekaren eller processen att hitta hyllan böckerna sitter på.
Om vår verksamhet är att eliminera bibliotekarie och skapa en självbetjäningsmekanism i vårt bibliotek, kanske vi anser att SRP respekteras i vårt första exempel. Läsarna är våra bibliotekarier också, de måste gå och hitta boken själva och sedan kolla in det på det automatiska systemet. Detta är också en möjlighet. Det som är viktigt att komma ihåg här är att du alltid måste överväga din verksamhet noggrant.
Principen om ensam ansvar bör alltid beaktas när vi skriver kod. Klass- och moduldesign påverkas starkt av det och det leder till en lågkopplad design med mindre och lättare beroende. Men som något mynt har det två ansikten. Det är frestande att designa från början av vår ansökan med SRP i åtanke. Det är också frestande att identifiera så många aktörer som vi vill eller behöver. Men det här är faktiskt farligt - ur en designsynvinkel - för att försöka tänka på alla parter från början. Överdriven SRP-övervägning kan enkelt leda till för tidig optimering och i stället för en bättre design kan det leda till en spridning där det tydliga ansvaret för klasser eller moduler kan vara svårt att förstå.
Så, när du observerar att en klass eller modul börjar förändras av olika anledningar, tveka inte, vidta nödvändiga åtgärder för att respektera SRP, men försena inte det eftersom för tidig optimering lätt kan lura dig.