Denne nybegynnervennlige artikkelen dekker en annen runde lukter og refactorings du bør gjøre deg kjent med tidlig i karrieren din. Vi dekker casestudier, polymorfisme, nullobjekter og dataklasser.
Denne kan også bli kalt "sjekkliste lukt" eller noe. Saksuttalelser er en lukt fordi de forårsaker duplisering - de er ofte også ulovlige. De kan også føre til unødvendig store klasser, fordi alle disse metodene som reagerer på de ulike (og potensielt voksende) saks scenariene ofte kommer opp i samme klasse - som da har alle slags blandede ansvar. Det er ikke et sjeldent tilfelle at du har mange private metoder som ville være bedre i klasser av seg selv.
Et stort problem med saksangivelser oppstår hvis du vil utvide dem. Da må du endre den spesielle metoden - muligens igjen og igjen. Og ikke bare der, fordi de ofte har tvillinger gjentatt over alt som nå trenger en oppdatering også. En fin måte å avle bugs for sikker. Som du kanskje husker, vil vi være åpne for utvidelse, men lukket for endring. Her er endring uunngåelig og bare et spørsmål om tid.
Du gjør det vanskeligere for deg selv å trekke ut og gjenbruke kode, pluss at det er en tikkende rotbombe. Ofte viser koden avhenger av slike saksopplysninger, som deretter dupliserer lukten og åpner porten åpen for en runde hagleoperasjon i fremtiden. Au! Også spørre en gjenstand før du finner den riktige metoden for å utføre, er et ekkel brudd på "Tell-Don't-Ask" -prinsippet.
Det er en god teknikk for å håndtere behovet for saksoppgaver. Fancy ord innkommende! polymorfisme. Dette lar deg lage det samme grensesnittet for forskjellige objekter og bruke det som er nødvendig for ulike scenarier. Du kan bare bytte i riktig objekt og det tilpasser seg dine behov fordi det har samme metoder på den. Deres oppførsel under disse metodene er annerledes, men så lenge objektene reagerer på det samme grensesnittet, bryr seg ikke Ruby. For eksempel, VegetarianDish.new.order
og VeganDish.new.order
oppfører seg annerledes, men begge svarer på #rekkefølge
den samme veien. Du vil bare bestille og ikke svare på tonnevis av spørsmål som om du spiser egg eller ikke.
Polymorfisme er implementert ved å trekke ut en klasse for saksoppgaven og flytte den logikken til en ny metode på den klassen. Du fortsetter å gjøre det for hvert ben i det betingede treet og gi dem alle samme metode navn. På den måten innkapsler du atferden til et objekt som er best egnet til å ta den typen av beslutning, og har ingen grunn til å endre seg videre. Se, på den måten kan du unngå alle disse gnissende spørsmålene på et objekt - du forteller bare hva den skal gjøre. Når behovet oppstår for mer betingede tilfeller, oppretter du bare en annen klasse som tar vare på det ene ansvaret under samme metode navn.
Case Statement Logic
klassen Operation def price case @mission_tpe når: counter_intelligence @standard_fee + @counter_intelligence_fee når: terrorisme @standard_fee + @terrorism_fee når: hevn @standard_fee + @revenge_fee når: extortion @standard_fee + @extortion_fee endeendens ende counter_intel_op = Operation.new (oppgave_type: : counter_intelligence) counter_intel_op.price terror_op = Operation.new (oppdragstype:: terrorisme) terror_op.price revenge_op = Operation.new (oppgave_type:: hevn) revenge_op.price extortion_op = Operation.new (oppdragstype:: utpressing) extortion_op.price
I vårt eksempel har vi en Operasjon
klasse som trenger å spørre om sin mission_type
før det kan fortelle deg prisen. Det er lett å se at dette pris
Metoden venter bare på å endres når en ny type operasjon blir lagt til. Når du vil vise det i ditt syn også, må du bruke den endringen der også. (FYI, for synspunkter kan du bruke polymorfe partier i Rails for å unngå at disse saksdeklarasjonene blastes over hele visningen din.)
Polymorfe klasser
klasse CounterIntelligenceOperation def price @standard_fee + @counter_intelligence_fee sluttklasse klasse TerrorismOperation def pris @standard_fee + @terrorism_fee slutten end klasse RevengeOperation def price @standard_fee + @revenge_fee slutten end klasse ExtortionOperation def price @standard_fee + @extortion_fee endeend counter_intel_op = CounterIntelligenceOperation.new counter_intel_op .price terror_op = CounterIntelligenceOperation.new terror_op.price revenge_op = CounterIntelligenceOperation.new revenge_op.price extortion_op = CounterIntelligenceOperation.new extortion_op.price
Så i stedet for å gå ned på kaninhullet, opprettet vi en rekke operasjonsklasser som har egen kunnskap om hvor mye deres avgifter legger opp til sluttprisen. Vi kan bare fortelle dem å gi oss prisen. Du trenger ikke alltid å bli kvitt den opprinnelige (Operasjon
) klasse-bare når du finner ut at du har hentet all den kunnskapen den hadde.
Jeg tror at logikken bak saken er uunngåelig. Scenarier hvor du må gå gjennom en slags sjekkliste før du finner objektet eller oppførselen som kommer til å gjøre jobben, er bare for vanlig. Spørsmålet er ganske enkelt hvordan de håndteres best. Jeg er for ikke å gjenta dem og bruke verktøyene objektorientert programmering gir meg for å designe diskrete klasser som enkelt kan byttes ut gjennom grensesnittet.
Å sjekke for null over alt er en spesiell type tilfelle setning lukt. Å stille et objekt om null er ofte en slags skjult saksoppgave. Håndtering av null betingelsesmessig kan ha formen av object.nil?
, object.present?
, object.try
, og deretter en slags handling i saken nil
dukker opp på festen din.
Et annet mer lurt spørsmål er å stille et objekt om sin truthiness-ergo hvis det eksisterer eller hvis det er null - og deretter ta en handling. Ser ufarlig ut, men det er bare en forkledning. Ikke la deg lure: ternære operatører eller ||
operatører faller også inn i denne kategorien selvfølgelig. Sett annerledes, conditionals ikke bare vises tydelig identifiserbar som med mindre
, if-else
eller sak
uttalelser. De har subtilere måter å krasje festen din på. Ikke spør objekter for deres nil-ness, men fortell dem om objektet for din lykkelige sti er fraværende at et null objekt nå har ansvaret for å svare på dine meldinger.
Null gjenstander er vanlige klasser. Det er ikke noe spesielt med dem - bare et "fancy navn". Du trekker ut noen betinget logikk relatert til nil
og så behandler du det polymorphically. Du inneholder den atferden, kontrollerer strømmen av appen din via disse klassene, og har også objekter som er åpne for andre utvidelser som passer dem. Tenk på hvordan a Prøve
(NullSubscription
) klassen kunne vokse over tid. Ikke bare er det mer tørt og yadda-yadda-yadda, det er også mye mer beskrivende og stabilt.
En stor fordel ved å bruke null gjenstander er at ting ikke kan blåse opp så enkelt. Disse objektene svarer på de samme meldingene som emulene emulert - du trenger ikke alltid å kopiere hele API-en til null objekter selvfølgelig, noe som gir appen din liten grunn til å bli gal. Vær imidlertid oppmerksom på at null objekter innkapsler betinget logikk uten å fjerne den helt. Du finner bare et bedre hjem for det.
Siden du har mye nølelatert handling i appen din, er det ganske smittsom og usunn for appen din, jeg liker å tenke på null gjenstander som "Nil Containment Pattern" (Vennligst ikke saksøke meg!). Hvorfor smittsom? Fordi hvis du passerer nil
rundt, noe annet sted i ditt hierarki, en annen metode er før eller senere også tvunget til å spørre om nil
er i byen, som da fører til en annen runde av å ta motforanstaltninger for å håndtere et slikt tilfelle.
Sett annerledes, det er ikke kult å si at det ikke er kjekt å smitte på fordi det er smittsom. Be om gjenstander for nil
er mest sannsynlig alltid et symptom på dårlig design-ingen fornærmelse og ikke føler seg dårlig! -Vi har alle vært der. Jeg ønsker ikke å gå "willy-nilly" om hvordan unfriendly null kan være, men noen ting må nevnes:
Samlet sett kommer scenariet som et objekt mangler noe, opp veldig ofte. Et ofte sitert eksempel er en app som har en registrert Bruker
og a NilUser
. Men siden brukere som ikke eksisterer, er et dumt konsept hvis den personen tydelig ser på appen din, er det sannsynligvis kjøligere å ha en Gjest
som ikke har registrert seg ennå. Et manglende abonnement kan være en Prøve
, en null ladning kan være a freebie
, og så videre.
Å nevne nullobjekter er noen ganger åpenbare og enkle, noen ganger super harde. Men prøv å holde seg borte fra å navngi alle null gjenstander med en ledende "Null" eller "Nei". Du kan gjøre det bedre! Å gi litt kontekst går langt, tror jeg. Velg et navn som er mer spesifikt og meningsfylt, noe som gjenspeiler det faktiske brukstilfellet. På den måten kommuniserer du tydeligere til andre teammedlemmer og til ditt fremtidige selvfølgelig selvfølgelig.
Det er et par måter å implementere denne teknikken på. Jeg skal vise deg en som jeg synes er grei og nybegynnerlig. Jeg håper det er et godt grunnlag for å forstå mer involverte tilnærminger. Når du gjentatte ganger støter på en slags betinget som omhandler nil
, du vet at det er på tide å bare lage en ny klasse og flytte den atferden over. Etter det lar du den opprinnelige klassen vite at denne nye spilleren nå handler med ingenting.
I eksemplet nedenfor kan du se at Spekter
klassen spør litt for mye om nil
og clutters opp koden unødvendig. Det ønsker å sikre at vi har en evil_operation
før den bestemmer seg for å lade. Kan du se brudd på "Tell-Don't-Ask"?
En annen problematisk del er hvorfor Specter trenger å bry seg om implementeringen av en nullpris. De prøve
Metoden spør også sneakily om evil_operation
har en pris
å håndtere ingenting via eller
(||
) uttalelse. evil_operation.present?
gjør den samme feilen. Vi kan forenkle dette:
klasse Specter inkluderer ActiveModel :: Modell attr_accessor: credit_card,: evil_operation def charge med mindre evil_operation.nil? evil_operation.charge (credit_card) slutten def def_discount? evil_operation.present? && evil_operation.has_discount? avslutte def price evil_operation.try (: price) || 0 slutten klasse EvilOperation inkluderer ActiveModel :: Modell attr_accessor: rabatt,: pris def har_discount? rabatt ende def charge (credit_card) credit_card.charge (pris) slutten
klasse NoOperation def charge (kredittkort) "No evil operation registered" slutten def has_discount? falsk sluttdefinisjon 0 sluttklasse Spektre inkluderer ActiveModel :: Modell attr_accessor: credit_card,: evil_operation def charge evil_operation.charge (credit_card) ende def has_discount? evil_operation.has_discount? slutt sluttpris evil_operation.price avslutte privat def evil_operation @evil_operation || NoOperation.new slutten klasse EvilOperation inkluderer ActiveModel :: Modell attr_accessor: rabatt,: pris def har_discount? rabatt ende def charge (credit_card) credit_card.charge (pris) slutten
Jeg antar at dette eksemplet er enkelt nok til å se med det samme hvor elegante nullobjekter kan være. I vårt tilfelle har vi en NoOperation
null klasse som vet hvordan man skal håndtere en ikke-eksisterende ond operasjon via:
0
beløp.Vi opprettet denne nye klassen som omhandler ingenting, et objekt som håndterer fraværet av ting, og bytter det inn i den gamle klassen hvis nil
dukker opp. API-en er nøkkelen her, fordi hvis den samsvarer med den opprinnelige klassen, kan du bytte null objektet med sømløs verdier vi trenger. Det var akkurat det vi gjorde i den private metoden Specter # evil_operation
. Hvis vi har evil_operation
objekt, vi trenger vi bruker det; Hvis ikke, bruker vi vår nil
kameleon som vet hvordan man skal håndtere disse meldingene, så evil_operation
vil aldri returnere null lenger. Duck skriver på sitt beste.
Vår problematiske betingede logikk er nå pakket inn på ett sted, og vårt nullobjekt har ansvaret for atferden Spekter
ser etter. TØRKE! Vi har nettopp bygget det samme grensesnittet fra det opprinnelige objektet som ikke kunne håndtere. Husk at ingen gjør en dårlig jobb ved å motta meldinger - ingen hjemme, alltid! Fra nå av er det bare å fortelle ting hva de skal gjøre uten å spørre dem først for deres "tillatelse". Hva er også kult er at det ikke var behov for å røre på EvilOperation
klasse i det hele tatt.
Sist men ikke minst, ble jeg kvitt sjekken om en ond operasjon er til stede i Specter # has_discount?
. Du trenger ikke å sørge for at en operasjon eksisterer for å få rabatten. Som et resultat av null objektet, den Spekter
klassen er mye slankere og deler ikke andre klassers ansvar så mye.
En god retningslinje for å ta bort dette er å ikke kontrollere objekter hvis de er tilbøyelige til å gjøre noe. Beordre dem som en borsergeant. Som det er så ofte, er det sannsynligvis ikke kult i det virkelige liv, men det er godt råd for Objektorientert Programmering. Gi meg nå 20!
Generelt gjelder alle fordelene ved å bruke Polymorphism i stedet for saksangivelser også for Null Objects. Tross alt. det er bare et spesielt tilfelle av saksreklamer. Det samme gjelder for ulemper:
La oss lukke denne artikkelen med noe lys. Dette er en lukt fordi det er en klasse som ikke har noen oppførsel, bortsett fra å få og sette inn dataene sine - det er egentlig ikke noe mer enn en databeholder uten ekstra metoder som gjør noe med dataene. Det gjør dem passive i naturen fordi disse dataene holdes der for å bli brukt av andre klasser. Og vi vet allerede at dette ikke er et ideelt scenario fordi det skriker har misunnelse. Vanligvis ønsker vi å ha klasser som har tilstandsbetydende data de tar vare på - så vel som atferd via metoder som kan virke på disse dataene uten mye hindring eller snooping i andre klasser.
Du kan begynne å refactoring klasser som disse ved å muligens trekke ut adferd fra andre klasser som virker på dataene i dataklassen. Ved å gjøre det kan du sakte tiltrekke seg nyttig oppførsel for dataene og gi den et riktig hjem. Det er helt fint hvis disse klassene vokser oppførsel over tid. Noen ganger kan du flytte en hel metode over enkelt, og noen ganger må du trekke ut deler av en større metode først og deretter trekke den inn i dataklassen. Når du er i tvil, hvis du kan redusere tilgangen som andre klasser har på dataklassen din, bør du definitivt gå for det og flytte den atferden over. Målet ditt bør være å pensjonere på et tidspunkt de getters og setters som ga andre objekter tilgang til dataene. Som et resultat vil du ha lavere kobling mellom klasser-som alltid er en seier!
Se, det var ikke så komplisert! Det er mange fancy lingo og kompliserte lydteknikker rundt kode lukter, men forhåpentligvis innså du at lukter og deres refactorings også deler et par trekk som er begrenset i antall. Kode lukter vil aldri gå vekk eller bli irrelevant - i hvert fall ikke før kroppene våre blir forsterket av AIs som la oss skrive den typen kvalitetskode vi er lysår borte fra for øyeblikket.
I løpet av de tre siste artiklene har jeg presentert deg en god del av de viktigste og mest vanlige kode lukt scenariene du vil støte på i løpet av din objektorienterte programmerings karriere. Jeg tror dette var en god introduksjon til nybegynnere på dette emnet, og jeg håper at kodene var gode nok til å følge tråden.
Når du har funnet ut disse prinsippene for å designe kvalitetskode, vil du plukke opp nye lukter og deres refactorings på kort tid. Hvis du har gjort det så langt og føler at du ikke miste det store bildet, tror jeg du er klar til å nærme seg sjefnivå OOP status.