SOLID Del 1 - Prinsippet om enkelt ansvar

Enkelt ansvar (SRP), Åpne / Lukke, Liskovs Substitution, Interface Segregation, og Dependency Inversion. Fem smidige prinsipper som skal veilede deg hver gang du skriver kode.

Definisjonen

En klasse skal bare ha en grunn til å forandre seg.

Definert av Robert C. Martin i sin bok Agile Software Development, Principles, Patterns, and Practices og senere publisert i C # versjonen av boken Agile Prinsipper, Mønstre og Practices i C #, er det en av de fem SOLID agile prinsippene. Det som står fast er veldig enkelt, men å oppnå at enkelheten kan være veldig vanskelig. En klasse skal bare ha en grunn til å forandre seg.

Men hvorfor? Hvorfor er det så viktig å ha bare én grunn til endring?

I statisk skrivte og kompilerte språk kan flere grunner føre til flere uønskede omplasseringer. Hvis det er to forskjellige grunner til å endre, er det tenkelig at to forskjellige lag kan fungere på samme kode av to forskjellige grunner. Hver må distribuere sin løsning, som i tilfelle av et sammensatt språk (som C ++, C # eller Java), kan føre til inkompatible moduler med andre lag eller andre deler av søknaden.

Selv om du kanskje ikke bruker et kompilert språk, kan det hende du må omprøve samme klasse eller modul av forskjellige grunner. Dette betyr mer QA-arbeid, tid og innsats.

Publikum

Å bestemme det ene ansvaret en klasse eller en modul skal ha, er mye mer kompleks enn bare å se på en sjekkliste. For eksempel, et ledd å finne årsakene til endring er å analysere publikum for vår klasse. Brukerne av programmet eller systemet vi utvikler som serveres av en bestemt modul, vil være de som ber om endringer i den. De som serveres, vil be om endring. Her er et par moduler og deres mulige publikum.

  • Persistensmodul - Publikum inkluderer DBAer og programvarearkitekter.
  • Rapporteringsmodul - Publikum inkluderer klerkere, regnskapsførere og operasjoner.
  • Betalingsberegningsmodul for et lønnsystem - Publikum kan inkludere advokater, ledere og regnskapsførere.
  • Book Search Module for et Library Management System - Publikum kan inkludere bibliotekar og / eller klientene selv.

Roller og skuespillere

Det kan være vanskelig å knytte konkrete personer til alle disse roller. I et lite selskap kan det være nødvendig med en enkelt person å tilfredsstille flere roller, mens i et stort selskap kan det være flere personer som er allokert til en enkelt rolle. Så det virker mye mer rimelig å tenke på rollene. Men roller av seg selv er ganske vanskelig å definere. Hva er en rolle? Hvordan finner vi det? Det er mye lettere å forestille seg at aktørene gjør disse rollene og forbinder publikum med disse skuespillerne.

Så hvis publikum definerer årsaker til endring, definerer skuespillerne publikum. Dette hjelper oss sterkt til å redusere begrepet konkrete personer som "John the Architect" til arkitektur, eller "Mary referent" til Operations.

Så et ansvar er en familie av funksjoner som tjener en bestemt skuespiller. (Robert C. Martin)

Kilde for endring

I betydningen av denne begrunnelsen blir skuespillere en kilde til forandring for familien av funksjoner som tjener dem. Etter hvert som deres behov endres, må den spesifikke familien av funksjoner også forandres for å imøtekomme deres behov.

En skuespiller for ansvar er den eneste kilden til forandring for det ansvaret. (Robert C. Martin)

Klassiske eksempler

Objekter som kan "skrive ut" seg selv

La oss si at vi har en Bok klasse innkapsler begrepet en bok og dens funksjoner.

klasse bok funksjon getTitle () return "en stor bok";  funksjon getAuthor () return "John Doe";  funksjon turnPage () // peker til neste side funksjon printCurrentPage () echo "nåværende sideinnhold"; 

Dette kan se ut som en rimelig klasse. Vi har en bok, den kan gi tittelen, forfatteren, og den kan slå på siden. Endelig er det også i stand til å skrive ut den nåværende siden på skjermen. Men det er et lite problem. Hvis vi tenker på aktørene som er involvert i driften av Bok objekt, hvem kan de være? Vi kan lett tenke på to forskjellige aktører her: Book Management (som bibliotekar) og Data Presentasjonsmekanisme (som måten vi vil levere innholdet til brukeren på skjermen, grafisk brukergrensesnitt, kun tekstbrukergrensesnitt, kanskje utskrift) . Dette er to veldig forskjellige skuespillere.

Det er dårlig å blande forretningslogikk med presentasjon, fordi det er i strid med prinsippet om enkelt ansvar (SRP). Ta en titt på følgende kode:

klasse bok funksjon getTitle () return "en stor bok";  funksjon getAuthor () return "John Doe";  funksjon turnPage () // peker til neste side funksjon getCurrentPage () return "nåværende sideinnhold";  grensesnitt Skriver funksjon printPage ($ side);  klasse PlainTextPrinter implementerer skriveren funksjon printPage ($ side) echo $ page;  klasse HtmlPrinter implementerer skriveren funksjon printPage ($ side) echo '
'. $ side. '
';

Selv dette svært grunnleggende eksemplet viser hvordan å skille presentasjon fra forretningslogikk, og respektere SRP, gir store fordeler i designets fleksibilitet.

Objekter som kan "lagre" seg selv

Et lignende eksempel på det som er over er når et objekt kan lagre og hente seg fra presentasjonen.

klasse bok funksjon getTitle () return "en stor bok";  funksjon getAuthor () return "John Doe";  funksjon turnPage () // peker til neste side funksjon getCurrentPage () return "nåværende sideinnhold";  funksjonen lagre () $ filename = '/ documents /'. $ Dette-> getTitle (). '-'. $ Dette-> getAuthor (); file_put_contents ($ filnavn, serialize ($ this)); 

Vi kan igjen identifisere flere aktører som Book Management System og Persistence. Når vi vil endre utholdenhet, må vi endre denne klassen. Når vi vil endre hvordan vi får fra en side til den neste, må vi endre denne klassen. Det er flere endringsakser her.

klasse bok funksjon getTitle () return "en stor bok";  funksjon getAuthor () return "John Doe";  funksjon turnPage () // peker til neste side funksjon getCurrentPage () return "nåværende sideinnhold";  klasse SimpleFilePersistence funksjon lagre (Book $ bok) $ filename = '/ documents /'. $ book-> getTitle (). '-'. $ Bok-> getAuthor (); file_put_contents ($ filnavn, serialize ($ bok)); 

Å flytte utholdenhetsoperasjonen til en annen klasse vil tydelig skille ansvaret, og vi vil være fri til å utveksle utholdenhetsmetoder uten å påvirke vår Bok klasse. For eksempel implementere a DatabasePersistence klassen ville være trivial og vår forretningslogikk bygget rundt operasjoner med bøker vil ikke forandre seg.

En høyere nivåvisning

I mine tidligere artikler nevnte jeg ofte og presenterte det høye arkitektoniske skjemaet som kan sees nedenfor.


Hvis vi analyserer dette skjemaet, kan du se hvordan prinsippet om enkelt ansvar blir respektert. Objektopprettelse er adskilt til høyre i fabrikkene og hovedinngangen til søknaden vår, en aktør ett ansvar. Persistens er også tatt vare på i bunnen. En egen modul for eget ansvar. Til slutt, til venstre, har vi presentasjon eller leveringsmekanisme hvis du ønsker det, i form av en MVC eller en hvilken som helst annen type brukergrensesnitt. SRP respekteres igjen. Alt som gjenstår er å finne ut hva du skal gjøre inne i forretningslogikken.

Overvejelser om programvaredesign

Når vi tenker på programvaren vi trenger å skrive, kan vi analysere mange forskjellige aspekter. For eksempel kan flere krav som påvirker samme klasse representere en forandringsakse. Disse endringsaksene kan være et ledd for et enkelt ansvar. Det er stor sannsynlighet for at grupper av krav som påvirker samme gruppe funksjoner vil ha grunner til å endre eller bli spesifisert i utgangspunktet.

Den primære verdien av programvare er enkel endring. Den sekundære er funksjonalitet, i den hensikt å tilfredsstille så mange krav som mulig, og møte brukerens behov. For å oppnå en høy sekundær verdi er imidlertid en primærverdi obligatorisk. For å holde vår primære verdi høy, må vi ha et design som er lett å endre, forlenge, tilpasse nye funksjonaliteter og for å sikre at SRP respekteres.

Vi kan begrunnelse i en trinnvis måte:

  1. Høy primær verdi fører i tid til høy sekundær verdi.
  2. Sekundær verdi betyr brukernes behov.
  3. Brukerens behov betyr behovene til skuespillerne.
  4. Behovene til skuespillerne bestemmer behovene til endringer av disse skuespillerne.
  5. Behov for endring av skuespillere definerer vårt ansvar.

Så når vi designer vår programvare, bør vi:

  1. Finn og definer skuespillerne.
  2. Identifiser ansvaret som tjener disse aktørene.
  3. Grupper våre funksjoner og klasser slik at hver kun har ett tildelt ansvar.

Et mindre åpenbart eksempel

klasse bok funksjon getTitle () return "en stor bok";  funksjon getAuthor () return "John Doe";  funksjon turnPage () // peker til neste side funksjon getCurrentPage () return "nåværende sideinnhold";  funksjon getLocation () // returnerer stillingen i biblioteket // ie. hylle nummer og romnummer

Nå kan dette virke helt fornuftig. Vi har ingen metode som omhandler utholdenhet eller presentasjon. Vi har vår turnPage () funksjonalitet og noen få metoder for å gi forskjellig informasjon om boken. Vi kan imidlertid ha et problem. For å finne ut, kan vi analysere applikasjonen vår. Funksjonen getLocation () kan være problemet.

Alle metodene til Bok Klassen handler om forretningslogikk. Så vårt perspektiv må være fra bedriftens synspunkt. Hvis søknaden vår er skrevet for å bli brukt av ekte bibliotekarer som søker etter bøker og gir oss en fysisk bok, kan SRP bli brutt.

Vi kan begrunne at skuespillernes operasjon er de som er interessert i metodene getTitle (), getAuthor () og getLocation (). Klientene kan også ha tilgang til søknaden for å velge en bok og lese de første sidene for å få en ide om boken og avgjøre om de vil ha det eller ikke. Så skuespillernes lesere kan være interessert i alle metodene bortsett fra getLocations (). En vanlig klient bryr seg ikke om hvor boken holdes i biblioteket. Boken vil bli overlevert til klienten av bibliotekaristen. Så, vi har faktisk et brudd på SRP.

klasse bok funksjon getTitle () return "en stor bok";  funksjon getAuthor () return "John Doe";  funksjon turnPage () // peker til neste side funksjon getCurrentPage () return "nåværende sideinnhold";  klasse BookLocator funksjonssted (Book $ book) // returnerer stillingen i biblioteket // ie. hylle nummer og romnummer $ libraryMap-> findBookBy ($ book-> getTitle (), $ book-> getAuthor ()); 

Innføring av BookLocator, bibliotekaristen vil være interessert i BookLocator. Klienten vil være interessert i Bok bare. Selvfølgelig er det flere måter å implementere en BookLocator. Den kan bruke forfatteren og tittelen eller et bokobjekt og få den nødvendige informasjonen fra Bok. Det avhenger alltid av vår virksomhet. Det som er viktig er at hvis biblioteket endres, og bibliotekareren må finne bøker i et annerledes organisert bibliotek, vil Bok objektet vil ikke bli påvirket. På samme måte, hvis vi bestemmer oss for å gi en forhåndskompilert sammendrag til leserne i stedet for å la dem bla gjennom sidene, vil det ikke påvirke bibliotekaristen eller prosessen med å finne hyllen som bøkene sitter på.

Men hvis vår virksomhet er å eliminere bibliotekar og opprette en selvbetjeningsmekanisme i vårt bibliotek, kan vi vurdere at SRP respekteres i vårt første eksempel. Leserne er våre bibliotekarere også, de må gå og finne boken selv og deretter sjekke det ut på det automatiserte systemet. Dette er også en mulighet. Det som er viktig å huske her er at du alltid må vurdere virksomheten din nøye.

Siste tanker

Prinsippet om enkelt ansvar bør alltid vurderes når vi skriver kode. Klasse- og moduldesign er sterkt påvirket av det, og det fører til en lavkoblet design med mindre og lettere avhengigheter. Men som noen mynter har den to ansikter. Det er fristende å designe fra begynnelsen av vår søknad med SRP i tankene. Det er også fristende å identifisere så mange skuespillere som vi vil ha eller trenger. Men dette er faktisk farlig - fra et designsynspunkt - for å prøve å tenke på alle partiene helt fra begynnelsen. Overdreven SRP-vurdering kan lett føre til for tidlig optimalisering og i stedet for bedre design kan det føre til en spredt en hvor det klare ansvaret for klasser eller moduler kan være vanskelig å forstå.

Så når du observerer at en klasse eller en modul begynner å forandre seg av forskjellige grunner, ikke nøl med, ta de nødvendige skrittene for å respektere SRP, men gjør ikke forsinket fordi prematur optimalisering lett kan lure deg.