Programmering

Hvorfor getter- og settermetoder er onde

Jeg hadde ikke tenkt å starte en "er ond" -serie, men flere lesere ba meg forklare hvorfor jeg nevnte at du burde unngå å få / sette metoder i forrige måneds spalte, "Why extends Is Evil."

Selv om getter / setter-metoder er vanlig i Java, er de ikke spesielt objektorientert (OO). Faktisk kan de skade koden din vedlikehold. Videre er tilstedeværelsen av mange getter- og settermetoder et rødt flagg som programmet ikke nødvendigvis er godt designet fra et OO-perspektiv.

Denne artikkelen forklarer hvorfor du ikke bør bruke getters og settere (og når du kan bruke dem) og foreslår en designmetodikk som vil hjelpe deg med å bryte ut av getter / setter-mentaliteten.

Om designens natur

Før jeg starter i en annen designrelatert kolonne (med en provoserende tittel, ikke mindre), vil jeg avklare noen få ting.

Jeg ble forbauset over noen leserkommentarer som kom fra forrige måneds spalte, "Why extends Is Evil" (se Talkback på artikkelens siste side). Noen trodde at jeg hevdet at objektorientering bare er dårlig strekker har problemer, som om de to begrepene er likeverdige. Det er absolutt ikke det jeg tenkte Jeg sa, så la meg avklare noen metaproblemer.

Denne spalten og forrige måneds artikkel handler om design. Design er av natur en serie avveininger. Hvert valg har en god og dårlig side, og du tar ditt valg i sammenheng med overordnede kriterier definert av nødvendighet. Gode ​​og dårlige er imidlertid ikke absolutter. En god beslutning i en sammenheng kan være dårlig i en annen.

Hvis du ikke forstår begge sider av et problem, kan du ikke ta et intelligent valg; faktisk, hvis du ikke forstår alle konsekvensene av dine handlinger, designer du ikke i det hele tatt. Du snubler i mørket. Det er ikke en ulykke at hvert kapittel i Gang of Four's Design mønstre boken inneholder en "konsekvenser" -del som beskriver når og hvorfor bruk av et mønster er upassende.

Å si at noen språkfunksjoner eller vanlig programmeringsidiom (som tilbehør) har problemer, er ikke det samme som å si at du aldri skal bruke dem under noen omstendigheter. Og bare fordi en funksjon eller uttrykk ofte brukes, betyr ikke det deg bør bruk den heller. Uinformerte programmerere skriver mange programmer, og bare å være ansatt i Sun Microsystems eller Microsoft forbedrer ikke magisk noen andres programmerings- eller designevner. Java-pakkene inneholder mye god kode. Men det er også deler av den koden, jeg er sikker på at forfatterne er flau for å innrømme at de skrev.

På samme måte presser markedsførings- eller politiske insentiver ofte designidiomer. Noen ganger tar programmerere dårlige beslutninger, men bedrifter ønsker å markedsføre hva teknologien kan gjøre, så de understreker at måten du gjør det på er mindre enn ideelt. De gjør det beste ut av en dårlig situasjon. Derfor opptrer du uansvarlig når du tar i bruk programmeringsrutiner bare fordi "det er slik du skal gjøre ting." Mange mislykkede Enterprise JavaBeans-prosjekter (EJB) viser dette prinsippet. EJB-basert teknologi er god teknologi når den brukes riktig, men kan bokstavelig talt bringe et selskap ned hvis det brukes feil.

Poenget mitt er at du ikke skal programmere blindt. Du må forstå den ødeleggelsen en funksjon eller et uttrykk kan utrette. Når du gjør det, er du i en mye bedre posisjon til å bestemme om du skal bruke den funksjonen eller uttrykket. Dine valg bør være både informerte og pragmatiske. Hensikten med disse artiklene er å hjelpe deg med å nærme deg programmeringen din med åpne øyne.

Dataabstrahering

Et grunnleggende forskrift for OO-systemer er at et objekt ikke skal avsløre noen av dets implementeringsdetaljer. På denne måten kan du endre implementeringen uten å endre koden som bruker objektet. Det følger da at i OO-systemer bør du unngå getter- og setterfunksjoner siden de for det meste gir tilgang til implementeringsdetaljer.

For å se hvorfor, bør du vurdere at det kan være 1000 anrop til a getX () metode i programmet ditt, og hver samtale antar at returverdien er av en bestemt type. Du kan lagre getX ()returverdien i en lokal variabel, for eksempel, og den variabeltypen må samsvare med returverditypen. Hvis du trenger å endre måten objektet implementeres på en slik måte at typen X endres, er du i dype problemer.

Hvis X var en int, men nå må det være en langfår du 1000 kompileringsfeil. Hvis du feilaktig løser problemet ved å kaste returverdien til int, koden vil kompileres rent, men den fungerer ikke. (Returverdien kan bli avkortet.) Du må endre koden rundt hver av de 1000 samtalene for å kompensere for endringen. Jeg vil absolutt ikke gjøre så mye arbeid.

Et grunnleggende prinsipp for OO-systemer er dataabstrahering. Du bør helt skjule måten et objekt implementerer en meldingsbehandler fra resten av programmet. Det er en grunn til at alle forekomstvariablene dine (ikke-faste felt i en klasse) burde være privat.

Hvis du lager en forekomstvariabel offentlig, så kan du ikke endre feltet når klassen utvikler seg over tid fordi du ville ødelegge den eksterne koden som bruker feltet. Du vil ikke søke i 1000 bruksområder for en klasse bare fordi du endrer den klassen.

Dette implementeringshemmingsprinsippet fører til en god syretest av et OO-systems kvalitet: Kan du gjøre massive endringer i en klassedefinisjon - til og med kaste ut hele saken og erstatte den med en helt annen implementering - uten å påvirke noen av koden som bruker den klassens gjenstander? Denne typen modulering er den sentrale forutsetningen for objektorientering og gjør vedlikehold mye lettere. Uten implementering skjuler det seg lite å bruke andre OO-funksjoner.

Getter og setter metoder (også kjent som accessors) er farlige av samme grunn som offentlig felt er farlige: De gir ekstern tilgang til implementeringsdetaljer. Hva om du trenger å endre typen av tilgang til feltet? Du må også endre tilbehørets returtype. Du bruker denne returverdien flere steder, så du må også endre all den koden. Jeg vil begrense effekten av en endring til en enkelt klassedefinisjon. Jeg vil ikke at de skal ringe ut i hele programmet.

Siden aksessorer bryter innkapslingsprinsippet, kan du med rimelighet hevde at et system som tungt eller uhensiktsmessig bruker tilbehør, rett og slett ikke er objektorientert. Hvis du går gjennom en designprosess, i motsetning til bare koding, finner du knapt noen tilbehør i programmet ditt. Prosessen er viktig. Jeg har mer å si om denne saken på slutten av artikkelen.

Mangelen på getter / setter-metoder betyr ikke at noen data ikke strømmer gjennom systemet. Ikke desto mindre er det best å minimere dataflytting så mye som mulig. Min erfaring er at vedlikeholdsevnen er omvendt proporsjonal med datamengden som beveger seg mellom objekter. Selv om du kanskje ikke ser hvordan ennå, kan du faktisk eliminere det meste av denne databevegelsen.

Ved å utforme nøye og fokusere på hva du må gjøre i stedet for hvordan du skal gjøre det, eliminerer du de aller fleste getter / setter-metodene i programmet ditt. Ikke be om informasjonen du trenger for å gjøre arbeidet; spør objektet som har informasjonen om å gjøre jobben for deg. De fleste aksessorer finner veien til kode fordi designerne ikke tenkte på den dynamiske modellen: kjøretidsobjektene og meldingene de sender til hverandre for å gjøre jobben. De starter (feil) med å designe et klassehierarki, og prøver deretter å hore disse klassene inn i den dynamiske modellen. Denne tilnærmingen fungerer aldri. For å bygge en statisk modell, må du oppdage forholdet mellom klassene, og disse forholdene samsvarer nøyaktig med meldingsflyten. En tilknytning eksisterer bare mellom to klasser når objekter fra en klasse sender meldinger til objekter fra den andre. Den statiske modellens hovedformål er å fange denne tilknytningsinformasjonen mens du modellerer dynamisk.

Uten en klart definert dynamisk modell, gjetter du bare hvordan du vil bruke klassens objekter. Følgelig ender tilgangsmetoder ofte i modellen fordi du må gi så mye tilgang som mulig, siden du ikke kan forutsi om du trenger det eller ikke. Denne typen strategi for gjetningsstrategi er i beste fall ineffektiv. Du kaster bort tid på å skrive ubrukelige metoder (eller legge til unødvendige evner til klassene).

Tilbehør havner også i design etter vane. Når prosessuelle programmerere vedtar Java, har de en tendens til å starte med å bygge kjent kode. Prosessuelle språk har ikke klasser, men de har C struct (tenk: klasse uten metoder). Det virker da naturlig å etterligne a struct ved å bygge klassedefinisjoner med praktisk talt ingen metoder og ingenting annet enn offentlig Enger. Disse prosessuelle programmererne leser et sted at felt skal være privatimidlertid, så de lager feltene privat og forsyning offentlig tilgangsmetoder. Men de har bare komplisert offentligheten. De har absolutt ikke gjort systemet objektorientert.

Tegn deg selv

En forgrening av full feltinnkapsling er i brukergrensesnittkonstruksjon (UI). Hvis du ikke kan bruke aksessorer, kan du ikke ha en brukergrensesnittbyggerklasse som kaller a getAttribute () metode. I stedet har klasser elementer som tegn deg selv(...) metoder.

EN getIdentity () Metoden kan også fungere, forutsatt at den returnerer et objekt som implementerer Identitet grensesnitt. Dette grensesnittet må inneholde en tegn deg selv() (eller gi meg en-JKomponent-den-representerer-din-identitet) -metoden. Selv om getIdentity starter med "få", det er ikke en tilgangsstykke fordi den ikke bare returnerer et felt. Den returnerer et komplekst objekt som har rimelig oppførsel. Selv når jeg har en Identitet objekt, har jeg fortsatt ingen anelse om hvordan en identitet blir representert internt.

Selvfølgelig, en tegn deg selv() strategi betyr at jeg (gisp!) legger UI-kode inn i forretningslogikken. Tenk på hva som skjer når brukergrensesnittets krav endres. La oss si at jeg vil representere attributtet på en helt annen måte. I dag er en "identitet" et navn; i morgen er det navn og ID-nummer; dagen etter det er det et navn, ID-nummer og bilde. Jeg begrenser omfanget av disse endringene til ett sted i koden. Hvis jeg har en gi-meg-en-JKomponent-som-representerer-identitetsklassen din, så har jeg isolert måten identiteter blir representert fra resten av systemet.

Husk at jeg faktisk ikke har lagt inn noen brukergrensesnittkode i forretningslogikken. Jeg har skrevet brukergrensesnittlaget i form av AWT (Abstract Window Toolkit) eller Swing, som begge er abstraksjonslag. Den faktiske brukergrensesnittkoden er i AWT / Swing-implementeringen. Det er hele poenget med et abstraksjonslag - å isolere forretningslogikken din fra et undersystems mekanikk. Jeg kan enkelt portere til et annet grafisk miljø uten å endre koden, så det eneste problemet er litt rot. Du kan enkelt eliminere dette rotet ved å flytte all brukergrensesnittkoden til en indre klasse (eller ved å bruke fasademønsteret).

JavaBeans

Du kan protestere ved å si "Men hva med JavaBeans?" Hva med dem? Du kan absolutt bygge JavaBeans uten getters og setters. De BeanCustomizer, BeanInfo, og BeanDescriptor klasser eksisterer alle for akkurat dette formålet. JavaBean-spesifikasjonsdesignerne kastet getter / setter-idiomet inn i bildet fordi de trodde det ville være en enkel måte å raskt lage en bønne på - noe du kan gjøre mens du lærer hvordan du gjør det riktig. Dessverre gjorde ingen det.

Tilbehør ble opprettet utelukkende som en måte å merke bestemte egenskaper slik at et UI-builder-program eller tilsvarende kunne identifisere dem. Du skal ikke kalle disse metodene selv. De finnes for et automatisert verktøy å bruke. Dette verktøyet bruker API-er for introspeksjon i Klasse klasse for å finne metodene og ekstrapolere eksistensen av visse egenskaper fra metodenavnene. I praksis har ikke dette introspeksjonsbaserte uttrykket gått. Det har gjort koden veldig for komplisert og prosessuell. Programmerere som ikke forstår dataabstraksjon, ringer faktisk tilbehøret, og som en konsekvens er koden mindre vedlikeholdbar. Av denne grunn vil en metadatafunksjon bli innlemmet i Java 1.5 (forventes i midten av 2004). Så i stedet for:

privat int eiendom; public int getProperty () {return eiendom; } public void setProperty (int value} {property = value;} 

Du kan bruke noe sånt som:

privat @ eiendom int eiendom; 

UI-konstruksjonsverktøyet eller tilsvarende vil bruke introspeksjons-API-ene til å finne egenskapene, i stedet for å undersøke metodenavn og utlede en eiendoms eksistens fra et navn. Derfor skader ingen kjøretidstilbehør koden din.

Når er en tilbehør greit?

Først, som jeg diskuterte tidligere, er det greit at en metode returnerer et objekt når det gjelder et grensesnitt som objektet implementerer, fordi det grensesnittet isolerer deg fra endringer i implementeringsklassen. Denne typen metode (som returnerer en grensesnittreferanse) er egentlig ikke en "getter" i betydningen av en metode som bare gir tilgang til et felt. Hvis du endrer leverandørens interne implementering, endrer du bare definisjonen for det returnerte objektet for å imøtekomme endringene. Du beskytter fortsatt den eksterne koden som bruker objektet gjennom grensesnittet.

$config[zx-auto] not found$config[zx-overlay] not found