Programmering

Hvorfor getter- og settermetoder er onde

Jeg havde ikke til hensigt at starte en "er ond" -serie, men adskillige læsere bad mig om at forklare, hvorfor jeg nævnte, at du skulle undgå get / set-metoder i sidste måneds kolonne, "Why extends Is Evil."

Selvom getter / setter-metoder er almindelige i Java, er de ikke særligt objektorienterede (OO). Faktisk kan de skade din kodes vedligeholdelse. Desuden er tilstedeværelsen af ​​adskillige getter- og settermetoder et rødt flag, som programmet ikke nødvendigvis er godt designet ud fra et OO-perspektiv.

Denne artikel forklarer, hvorfor du ikke skal bruge getters og settere (og hvornår du kan bruge dem) og foreslår en designmetode, der hjælper dig med at bryde ud af getter / setter-mentaliteten.

Om designets art

Før jeg starter i en anden designrelateret kolonne (med en provokerende titel, ikke mindre), vil jeg afklare et par ting.

Jeg blev forbløffet over nogle læserkommentarer, der var resultatet af sidste måneds kolonne, "Why extends Is Evil" (se Talkback på artiklens sidste side). Nogle mennesker troede, at jeg argumenterede for, at objektorientering simpelthen er dårlig strækker sig har problemer, som om de to begreber er ækvivalente. Det er bestemt ikke, hvad jeg tanke Sagde jeg, så lad mig afklare nogle meta-problemer.

Denne kolonne og sidste måneds artikel handler om design. Design er af natur en række afvejninger. Hvert valg har en god og dårlig side, og du træffer dit valg i sammenhæng med overordnede kriterier defineret af nødvendighed. Godt og dårligt er dog ikke absolutter. En god beslutning i en sammenhæng kan være dårlig i en anden.

Hvis du ikke forstår begge sider af et problem, kan du ikke foretage et intelligent valg; faktisk, hvis du ikke forstår alle konsekvenserne af dine handlinger, designer du slet ikke. Du snubler i mørket. Det er ikke en ulykke, at hvert kapitel i Gang of Four's Designmønstre bogen indeholder en "konsekvenser" sektion, der beskriver, hvornår og hvorfor brugen af ​​et mønster er upassende.

At sige, at nogle sprogfunktioner eller almindeligt programmeringsidiom (som accessors) har problemer, er ikke det samme som at sige, at du aldrig bør bruge dem under nogen omstændigheder. Og bare fordi en funktion eller udtryk ofte bruges, betyder det ikke dig skulle gerne brug det enten. Uinformerede programmører skriver mange programmer, og simpelthen ved at være ansat i Sun Microsystems eller Microsoft forbedrer det ikke magisk nogens programmerings- eller designevner. Java-pakkerne indeholder en masse god kode. Men der er også dele af den kode, jeg er sikker på, at forfatterne er flov over at indrømme, at de skrev.

På samme måde skubber marketing eller politiske incitamenter ofte designidiomer. Nogle gange tager programmører dårlige beslutninger, men virksomheder vil promovere, hvad teknologien kan gøre, så de understreger, at den måde, du gør det på, er mindre end ideel. De gør det bedste ud af en dårlig situation. Derfor handler du uansvarligt, når du anvender enhver programmeringspraksis, simpelthen fordi "det er sådan, du skal gøre tingene." Mange mislykkede Enterprise JavaBeans-projekter (EJB) beviser dette princip. EJB-baseret teknologi er fantastisk teknologi, når den bruges korrekt, men kan bogstaveligt talt nedbringe en virksomhed, hvis den anvendes forkert.

Min pointe er, at du ikke skal programmere blindt. Du skal forstå den kaos, som en funktion eller udtryk kan skabe. Dermed er du i en meget bedre position til at beslutte, om du skal bruge den funktion eller formsprog. Dine valg skal være både informerede og pragmatiske. Formålet med disse artikler er at hjælpe dig med at komme til din programmering med åbne øjne.

Data abstraktion

Et grundlæggende bud for OO-systemer er, at et objekt ikke skal udsætte nogen af ​​dets implementeringsdetaljer. På denne måde kan du ændre implementeringen uden at ændre den kode, der bruger objektet. Det følger derefter, at i OO-systemer skal du undgå getter- og setterfunktioner, da de for det meste giver adgang til implementeringsoplysninger.

For at se hvorfor skal du overveje, at der muligvis er 1.000 opkald til a getX () metode i dit program, og hvert opkald antager, at returværdien er af en bestemt type. Du gemmer muligvis getX ()returneringsværdi i en lokal variabel, for eksempel, og den variabeltype skal matche returværditypen. Hvis du har brug for at ændre den måde, objektet implementeres på en sådan måde, at typen X ændrer sig, er du i dybe problemer.

Hvis X var en int, men nu skal der være en langfår du 1.000 kompileringsfejl. Hvis du fejlagtigt løser problemet ved at kaste returværdien til int, koden kompileres rent, men den fungerer ikke. (Returneringsværdien kan blive afkortet.) Du skal ændre koden omkring hvert af de 1.000 opkald for at kompensere for ændringen. Jeg vil bestemt ikke gøre så meget arbejde.

Et grundlæggende princip i OO-systemer er data abstraktion. Du skal helt skjule den måde, hvorpå et objekt implementerer en beskedhåndterer fra resten af ​​programmet. Det er en af ​​grundene til, at alle dine instansvariabler (en klasses ikke-konstante felter) skal være privat.

Hvis du laver en instansvariabel offentlig, så kan du ikke ændre feltet, da klassen udvikler sig over tid, fordi du ville bryde den eksterne kode, der bruger feltet. Du ønsker ikke at søge i 1.000 anvendelser af en klasse, simpelthen fordi du ændrer denne klasse.

Dette implementerings skjulingsprincip fører til en god syretest af et OO-systems kvalitet: Kan du foretage massive ændringer i en klassedefinition - endda smide det hele ud og erstatte det med en helt anden implementering - uden at påvirke nogen af ​​koden, der bruger den klassens genstande? Denne form for modulering er den centrale forudsætning for objektorientering og gør vedligeholdelse meget lettere. Uden at skjule implementering er der ringe mening at bruge andre OO-funktioner.

Getter og setter metoder (også kendt som accessors) er farlige af samme grund som offentlig felter er farlige: De giver ekstern adgang til implementeringsoplysninger. Hvad hvis du har brug for at ændre det felt, der er adgang til,? Du skal også ændre accessorens returtype. Du bruger denne returværdi adskillige steder, så du skal også ændre al denne kode. Jeg vil begrænse virkningerne af en ændring til en enkelt klassedefinition. Jeg vil ikke have dem til at krølle ud i hele programmet.

Da accessors overtræder indkapslingsprincippet, kan du med rimelighed hævde, at et system, der kraftigt eller uhensigtsmæssigt bruger accessors, simpelthen ikke er objektorienteret. Hvis du gennemgår en designproces i modsætning til bare kodning, finder du næppe nogen accessorer i dit program. Processen er vigtig. Jeg har mere at sige om dette spørgsmål i slutningen af ​​artiklen.

Manglen på getter / setter-metoder betyder ikke, at nogle data ikke strømmer gennem systemet. Ikke desto mindre er det bedst at minimere dataflytning så meget som muligt. Min erfaring er, at vedligeholdelsesevnen er omvendt proportional med mængden af ​​data, der bevæger sig mellem objekter. Selvom du måske ikke ser hvordan endnu, kan du faktisk fjerne det meste af denne dataflytning.

Ved at designe omhyggeligt og fokusere på, hvad du skal gøre, snarere end hvordan du gør det, fjerner du langt de fleste getter / setter-metoder i dit program. Bed ikke om de oplysninger, du har brug for til at udføre arbejdet; spørg det objekt, der har informationen til at udføre arbejdet for dig. De fleste accessors finder vej i kode, fordi designerne ikke tænkte på den dynamiske model: runtime-objekterne og de meddelelser, de sender til hinanden for at udføre arbejdet. De starter (forkert) med at designe et klassehierarki og forsøger derefter at hore disse klasser i den dynamiske model. Denne tilgang fungerer aldrig. For at opbygge en statisk model skal du opdage forholdet mellem klasserne, og disse forhold svarer nøjagtigt til meddelelsesflowet. Der er kun en sammenhæng mellem to klasser, når objekter fra en klasse sender beskeder til objekter fra den anden. Den statiske model har som hovedformål at indfange denne tilknytningsinformation, mens du modellerer dynamisk.

Uden en klart defineret dynamisk model gætter du kun på, hvordan du vil bruge en klasses objekter. Følgelig ender tilgangsmetoder ofte i modellen, fordi du skal give så meget adgang som muligt, da du ikke kan forudsige, om du har brug for det eller ej. Denne slags design-by-guessing-strategi er i bedste fald ineffektiv. Du spilder tid på at skrive ubrugelige metoder (eller tilføje unødvendige muligheder til klasserne).

Accessorer ender også i design efter vane. Når procedureprogrammerere vedtager Java, har de en tendens til at starte med at opbygge velkendt kode. Proceduremæssige sprog har ikke klasser, men de har C struct (tænk: klasse uden metoder). Det virker derfor naturligt at efterligne a struct ved at opbygge klassedefinitioner med næsten ingen metoder og intet andet end offentlig felter. Disse procedureprogrammerere læser et eller andet sted, at felter skal være privatdog, så de laver markerne privat og levering offentlig tilgangsmetoder. Men de har kun kompliceret offentlighedens adgang. De har bestemt ikke gjort systemet objektorienteret.

Tegn dig selv

En forgrening af fuld feltindkapsling er i brugergrænseflade (UI) -konstruktion. Hvis du ikke kan bruge accessors, kan du ikke få en UI-builder-klasse til at kalde a getAttribute () metode. I stedet har klasser elementer som tegn dig selv(...) metoder.

EN getIdentity () Metoden kan naturligvis også fungere, forudsat at den returnerer et objekt, der implementerer Identitet interface. Denne grænseflade skal indeholde en tegn dig selv() (eller giv mig en-JKomponent-den-repræsenterer-din-identitet) metode. Selvom getIdentity starter med "get", det er ikke en accessor, fordi det ikke bare returnerer et felt. Det returnerer et komplekst objekt, der har rimelig opførsel. Selv når jeg har en Identitet objekt, jeg har stadig ingen idé om, hvordan en identitet repræsenteres internt.

Naturligvis en tegn dig selv() strategi betyder, at jeg (gisp!) sætter UI-kode i forretningslogikken. Overvej hvad der sker, når brugergrænseflades krav ændres. Lad os sige, at jeg vil repræsentere attributten på en helt anden måde. I dag er en "identitet" et navn; i morgen er det et navn og ID-nummer; dagen efter er det et navn, ID-nummer og billede. Jeg begrænser omfanget af disse ændringer til et sted i koden. Hvis jeg har en give-me-a-JKomponent-den-repræsenterer-din-identitetsklasse, så har jeg isoleret den måde, identiteter er repræsenteret på fra resten af ​​systemet.

Husk, at jeg faktisk ikke har lagt nogen UI-kode i forretningslogikken. Jeg har skrevet UI-laget i form af AWT (Abstract Window Toolkit) eller Swing, som begge er abstraktionslag. Den aktuelle UI-kode er i implementeringen af ​​AWT / Swing. Det er hele pointen med et abstraktionslag - at isolere din forretningslogik fra et undersystems mekanik. Jeg kan nemt porte til et andet grafisk miljø uden at ændre koden, så det eneste problem er lidt rod. Du kan nemt fjerne dette rod ved at flytte al brugergrænseflade-koden ind i en indre klasse (eller ved at bruge facademønsteret).

JavaBeans

Du kan muligvis gøre indsigelse ved at sige "Men hvad med JavaBeans?" Hvad med dem? Du kan helt sikkert bygge JavaBeans uden getters og setters. Det BeanCustomizer, BeanInfoog BeanDescriptor klasser eksisterer alle til netop dette formål. JavaBean-spec-designerne kastede getter / setter-idiomet ind i billedet, fordi de troede, det ville være en nem måde hurtigt at fremstille en bønne på - noget du kan gøre, mens du lærer at gøre det rigtigt. Desværre gjorde ingen det.

Accessors blev oprettet udelukkende som en måde at mærke bestemte egenskaber på, så et UI-builder-program eller tilsvarende kunne identificere dem. Du skal ikke selv kalde disse metoder. De findes til et automatisk værktøj at bruge. Dette værktøj bruger introspektions-API'erne i Klasse klasse for at finde metoderne og ekstrapolere eksistensen af ​​visse egenskaber fra metodens navne. I praksis har dette introspektionsbaserede udtryk ikke fungeret. Det har gjort koden langt for kompliceret og proceduremæssig. Programmører, der ikke forstår dataabstraktion, kalder faktisk accessorerne, og koden er derfor mindre vedligeholdelig. Af denne grund vil en metadata-funktion blive indarbejdet i Java 1.5 (forventes medio 2004). Så i stedet for:

privat int ejendom; public int getProperty () {return property; } public void setProperty (int værdi} {ejendom = værdi;} 

Du kan bruge noget som:

privat @ ejendom int ejendom; 

UI-konstruktionsværktøjet eller tilsvarende bruger introspektions-API'erne til at finde egenskaberne i stedet for at undersøge metodenavne og udlede en ejendoms eksistens ud fra et navn. Derfor beskadiger ingen runtime accessor din kode.

Hvornår er en accessor okay?

For det første, som jeg diskuterede tidligere, er det okay for en metode at returnere et objekt i form af en grænseflade, som objektet implementerer, fordi grænsefladen isolerer dig fra ændringer til implementeringsklassen. Denne form for metode (der returnerer en interface-reference) er ikke rigtig en "getter" i betydningen af ​​en metode, der bare giver adgang til et felt. Hvis du ændrer udbyderens interne implementering, ændrer du bare det returnerede objekts definition for at imødekomme ændringerne. Du beskytter stadig den eksterne kode, der bruger objektet gennem dets grænseflade.

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