Tuesday, January 5th, 2010
Skriv en kommentar

Konseptet objektorientering (OO) ble født allerede på 60-tallet; Simula, som ble utviklet på Norsk Regnesentral med Ole-Johan Dahl og Kristen Nygaard i spissen, var det første objektorienterte programmeringsspråket. Det var likevel ikke før på 90-tallet at OO ble mainstream. I dag foregår det meste av moderne systemutvikling i objektorienterte språk.

Men det at man bruker et slikt språk er ikke det samme som at man driver med god objektorientering.

Objektorientering er å innkapsle detaljer! Man skjermer andre (objekter) fra å se/vite om detaljene, slik at de ikke gjør seg avhengig av disse detaljene. På den måten blir det større rom for å gjøre endringer – løsningene blir mere fleksible fordi man enkelt kan endre detaljene uten at det får bi-effekter. God OO skjuler detaljer godt, dårlig OO skjuler detaljer mindre godt.

Et ganske vanlig design som ikke er bra OO er systemer som inneholder to fundamentalt forskjellige typer objekter: (1) entiteter som først og fremst er bærere av data – som inneholder properties, og ikke så mye mer, og (2) objekter man typisk kaller controllere (eller handlere eller managere) som bruker den første typen, og som inneholder all logikken i systemet. Dette er et prosedyreorientert system, og det man bedriver kalles imperativ programmering – man instruerer hele tiden programmet om hva som skal gjøres, i stedet for å be objektene selv om å gjøre jobben.

Hvordan praktisere god OO?

Jeg forsøker hele tiden å lære både meg selv og andre hvordan man designer et system etter gode OO-prinsipper, og tror jeg har kommet frem til noen av de mest grunnleggende retningslinjene man bør følge – noen enkle huskeregler, eller knep – man bør ha i bakhodet når man programmerer. Prinsippene er ikke noe jeg selv har funnet på (langt i fra), men representerer de enkleste prinsippene man kan bruke hele tiden for å gå fra prosedyreorientering til god objektorientering.

Det første prinsippet er TELL, DON’T ASK! Si at objektet skal gjøre noe for deg, ikke spør det om informasjon. Implisit i dette er blant annet at man skal unngå getters og setters, dvs. properties i .net-klasser. Properties brukes til å spørre om et objekts tilstand, eller til å eksplisit endre tilstanden, og da skjuler man ikke lenger hvordan objektet fungerer.

Hvis du har kode som tar avgjørelser basert på tilstanden til et objekt, se om du i stedet kan flytte denne koden inn i objektet selv. Har du et slikt prosedyreorientert system som jeg nettopp beskrev, så forsøk å flytte kode ut fra handlerne (type 2) til dataobjektene (type 1). Gjør du det vil du snart se at propertiene blir overflødige.

Eller gå motsatt vei: Hvis du tar utgangspunkt i et objekt som nesten bare inneholder properties – undersøk hvor disse propertiesene brukes, og se om du kan flytte den koden til objektet.

Her følger et banalt eksempel. Du finner sjelden kode som er så tydelig feilplassert som dette her i ditt eget design, men det illustrerer i alle fall poenget mitt:

    1 public class DogManager

    2 {

    3     private void Manage(Dog dog)

    4     {

    5         if (dog.IsSleepy) dog.Sleep();

    6         else if (dog.IsHungry) dog.Eat();

    7         else if (dog.IsRestless) dog.BegOwnerForWalk();

    8         else dog.SniffSomething();

    9     }

   10 }

DogManager spør hunden om dens tilstand, og tar avgjørelser om hva den skal finne på basert på dette. Koden bør flyttes til Dog:

    1 public class Dog

    2 {

    3     public void DoSomething()

    4     {

    5         if (IsSleepy) Sleep();

    6         else if (IsHungry) Eat();

    7         else if (IsRestless) BegOwnerForWalk();

    8         else SniffSomething();

    9     }

Et prinsipp som er nært knyttet til Tell, Don’t Ask er LAW OF DEMETER (LoD). Denne “loven” sier at man skal begrense hva objekter vet om resten av systemet. Enheter (objekter/metoder) skal kun snakke med sine venner, ikke fremmede. Man begrenser altså koblingen mellom enhetene i systemet, og oppnår dermed blant annet færre bieffekter ved endringer.., slik som jeg var inne på tidligere.

LoD sier mer konkret at man i en gitt metode (M) i et objekt (O) kun kan kalle metoder på:
1) Objektet O selv
2) Parametre som sendes til metoden M
3) Objekter som opprettes i metoden M
4) Objekter som lever i O (objektets private variabler)

Man skal altså ikke kalle metoder på objekter man får returnert fra andre (via properties eller metodekall). Husker man hele tiden på dette når man koder vil man oppnå bedre OO. Her er en ny, banal illustrasjon:

    1 private void WalkTheDog(Dog theDog)

    2 {

    3     theDog.Legs.Move(); // violates LoD

    4 }

    5 

    6 private void WalkTheDog2(Dog theDog)

    7 {

    8     theDog.Walk(); // makes more sense..

    9 }

Det gir ikke mening at denne metoden skal vite noe om hvordan hunden fungerer – at bena må bevege seg for at hunden skal gå. Be heller hunden om å gå direkte, det er hunden som selv skal vite hvordan den fungerer (detaljene skal innkapsles og skjules).

Legg også merke til at brudd på LoD ofte kan gjenkjennes ved flere punktum: theDog punktum Legs punktum Move(). Dog er metodens nærmeste venn, Legs er en fremmed, og skal ikke kommuniseres med.

Den tredje og siste huskeregelen som vil gjøre deg til en bedre utvikler er at OO ikke nødvendigvis skal modellere virkeligheten! Dette er en vanlig misforståelse som mange av oss fikk innprentet da vi begynte å lære objektorientert programmering. Om vi begynner å designe et system ved å lage klasser av alle entitetene vi kan observere i det domenet vi skal modellere kommer vi ofte skjevt ut. Begynn i stedet med adferden du ønsker, og se hvilke objekter som “naturlig” kommer ut av det.

Skal vi for eksempel designe et kontrollsystem for en bil kan det være veldig feil å starte med å lage klasser for hjul, bremser, ratt, motor, bensintank, gir og bilen selv. Et system med klasser som drivstoffregulator, bremsekraftforsterker, oljenivå og servostyring vil kanskje bli enklere og mere oversiktelig. Testdreven utvikling (TDD) vil kunne hjelpe deg til å drive frem det beste designet.

På tide med en Oppsummering

De tre huskereglene jeg har presentert, sammen med de to kanskje aller viktigste programmeringsprinsippene – Single Responsibility (SRP) og Don’t Repeat Yourself (DRY) – vil sørge for at du designer kode som er enklere å forstå, og lar seg vedlikeholde, endre og utvide.

Fortell objektene hva de skal gjøre på enklest mulig måte, ikke spør de hva de holder på med. Mange properties (eller andre typer gettere og settere) betyr sansynligvis at du kan gjøre designet bedre. Ikke la objektene dine snakke med fremmede. Og ikke begynn med å modellere virkeligheten, driv i stedet frem objekter du trenger gjennom å starte med implementering av adferd.

Helt til slutt: Jeg snakker her om hvordan ideell, objektorientert kode ser ut. OO er ikke den optimale løsningen for alle problemer. Lager du f.eks. en enkel CRUD-applikasjon er full objektorientering overkill. OO egner seg bedre jo mere adferd systemet skal ha. Se for eksempel posten om Greg Young’s foredrag i Bergen, hvor han bruker OO på “command-siden” av systemet, men ikke på “query-delen”.

Noen eksterne lenker: Law of Demeter | Tell, Don’t Ask | Object-oriented programming | Single Responsibility Prinsiple | Don’t Repeat Yourself

PS: Denne blogposten begynte som en intern ZipTalk jeg holdt for utviklingsavdelingen i PSWinCom. Tittelen var da “OO > O”.

Kategorier: OO-design/clean code.
RSS feed for kommentarene. Tilbaketråkk.

2 kommentarer til “Enkle knep for bedre objektorientering”

  1. Jon Arild Tørresdal Says:

    Nice! Bra oppsummert. Jeg trodde jeg hadde god kontroll på dette, men bare ved å lese gjennom posten din fikk jeg noen nye ideer til 1) hvordan forbedre min egen kode ytterligere 2) hvordan kommunisere OO enklere til andre.

    Jeg er overbevist om at kodebaser rundt om kring hadde vært et ren fornøyelse, om alle utviklere hadde en komplett forståelse av OO :-)

    Føler at Object i OO blir litt some Test i TDD… Go BO og BDD! :-)

  2. Torbjørn Says:

    >> Føler at Object i OO blir litt some Test i TDD… Go BO og BDD

    Veldig godt sagt!

Skriv en kommentar

Tillatte tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Siste kommentarer

Torbjørn
PS: Takk til Børge Hansen, som delte SCARF-modellen med meg!...
Børge Hansen
Denne likte jeg veldig godt. Du skriver godt og har gode betraktninger  Keep it up – flere trenger å tørre å lære mer om ledelse – du l...
Tormod
Er egentlig ikke overrasket. F# sin fortè er programmererens produktivitet/kvalitet og anledning til parallell kjøring. Men kjøremotoren har ...
Stian
Ville også prøvd med et større problem (x100 eller x1000 f.eks). Når man snakker så små brøkdeler av et sekund som her så kan tiden for en ell...
Torbjørn
Har ikke sjekket - tar en titt i morgen hvis tid :)...
Einar W. Høst
Mhp tco: hva sier ILSpy?...
Torbjørn
Har ikke sett noe på PSeq før, men kjenner til den typen funksjoner fra blant annet Clojure. Og problemet med slike funksjoner i sammenhenger som de...
Håvard
Veldig bra sammenligning! Har du sett på ytelsen av PSeq.* fra powerpakken? Tipper den vil gi performancehit på små mengder, men kan kanskje resul...
Torbjørn
Jeg kom på en demonstrasjon-variant til jeg burde inkludere, nemlig bruk av list comprehension (en type computation expression (også kalt monads)). ...
Einar W. Høst
Interessant, det blir en trade-off mellom eleganse og fart på en måte. Den funksjonelle løsningen med vanlig filter er ren og pen, mens den imperat...
Creative Commons-lisens
Innholdet på denne bloggen er tilgjengelig under Creative Commons Navngivelse-Ikkekommersiell-DelPåSammeVilkår 3.0 Norge lisens.

Programmeringsbloggen
Kjempekjekt.com

© 2006-2013 Torbjørn Marø

Jeg har vært en profesjonell programmerer siden 1999, og dette er min blogg. Målet med bloggen er å stimulere meg selv og alle andre til kontinuerlig eksperimentering og læring.

Jeg forsøker å være allsidig, og programmerer blant annet i C#, Ruby, Erlang og Clojure.

Jeg praktiserer TDD og andre smidige utviklingspraksiser. Jeg er opptatt av kvalitet og ren kode.

Dette og ganske mye mer kan du lese om på denne bloggen!