Programmering

Mer om getters og settere

Det er et 25 år gammelt prinsipp for objektorientert (OO) design at du ikke skal utsette et objekts implementering for noen andre klasser i programmet. Programmet er unødvendig vanskelig å vedlikeholde når du avslører implementering, først og fremst fordi det å endre et objekt som avslører implementeringen, mandater til endringer i alle klassene som bruker objektet.

Dessverre bryter getter / setter-idiomet som mange programmerere tenker på som objektorientert dette fundamentale OO-prinsippet i spar. Tenk på eksemplet med a Penger klasse som har en getValue () metode på den som returnerer "verdien" i dollar. Du vil ha kode som følgende over hele programmet:

dobbel bestillingTotal; Pengebeløp = ...; //... orderTotal + = amount.getValue (); // orderTotal må være i dollar

Problemet med denne tilnærmingen er at den foregående koden gir en stor antagelse om hvordan Penger klasse er implementert (at "verdien" er lagret i en dobbelt). Kode som gjør implementeringsforutsetninger pause når implementeringen endres. Hvis du for eksempel trenger å internasjonalisere søknaden din for å støtte andre valutaer enn dollar, da getValue () returnerer ingenting meningsfylt. Du kan legge til en getCurrency (), men det ville gjøre all koden rundt getValue () ring mye mer komplisert, spesielt hvis du fortsetter å bruke getter / setter-strategien for å få den informasjonen du trenger for å gjøre jobben. En typisk (feil) implementering kan se slik ut:

Pengebeløp = ...; //... verdi = beløp.getValue (); valuta = beløp.getCurrency (); conversion = CurrencyTable.getConversionFactor (valuta, USDOLLARS); total + = verdi * konvertering; //...

Denne endringen er for komplisert til å håndteres av automatisk refactoring. Videre må du gjøre slike endringer overalt i koden din.

Den forretningslogiske løsningen på dette problemet er å gjøre jobben i objektet som har informasjonen som kreves for å utføre arbeidet. I stedet for å trekke ut "verdien" for å utføre noen ekstern operasjon på den, bør du ha Penger klassen utfører alle pengerelaterte operasjoner, inkludert valutakonvertering. Et riktig strukturert objekt vil håndtere totalen slik:

Penger totalt = ...; Pengebeløp = ...; total.increaseBy (beløp); 

De legge til() metoden vil finne ut valutaen til operanden, gjøre den nødvendige valutakonvertering (som egentlig er en operasjon på penger), og oppdater totalsummen. Hvis du til å begynne med brukte denne objekt-som-har-informasjonen-gjør-arbeidet-strategien, forestillingen om valuta kan legges til Penger klasse uten endringer som kreves i koden som bruker Penger gjenstander. Det vil si at arbeidet med å omorganisere kun dollar til en internasjonal implementering vil være konsentrert på ett sted: Penger klasse.

Problemet

De fleste programmerere har ingen problemer med å forstå dette konseptet på forretningslogisk nivå (selv om det kan ta litt innsats å konsekvent tenke slik). Problemer begynner imidlertid å dukke opp når brukergrensesnittet (UI) kommer inn i bildet. Problemet er ikke at du ikke kan bruke teknikker som den jeg nettopp beskrev for å bygge et brukergrensesnitt, men at mange programmerere er låst inn i en getter / setter-mentalitet når det gjelder brukergrensesnitt. Jeg klandrer dette problemet for fundamentalt prosessuelle kodekonstruksjonsverktøy som Visual Basic og dets kloner (inkludert Java UI-byggere) som tvinger deg inn i denne prosessuelle, getter / setter tankegangen.

(Digresjon: Noen av dere vil slå fra den forrige utsagnet og skrike at VB er basert på den hellige Model-View-Controller (MVC) -arkitekturen, det er også hellig. Husk at MVC ble utviklet for snart 30 år siden. Tidlig 1970-tallet var den største superdatamaskinen på nivå med dagens stasjonære maskiner. De fleste maskiner (som DEC PDP-11) var 16-biters datamaskiner, med 64 kB minne og klokkehastigheter målt i titalls megahertz. Brukergrensesnittet ditt var sannsynligvis et bunke med hullede kort. Hvis du var heldig nok til å ha en videoterminal, har du kanskje brukt et ASCII-basert konsollinngang / utgangssystem (I / U). Vi har lært mye de siste 30 årene. Selv Java Swing måtte erstatte MVC med en lignende "separerbar modell" -arkitektur, først og fremst fordi ren MVC ikke tilstrekkelig isolerer UI og domene-modellag.)

Så la oss definere problemet i et nøtteskall:

Hvis et objekt kanskje ikke avslører implementeringsinformasjon (gjennom get / set-metoder eller på noen annen måte), er det grunn til at et objekt på en eller annen måte må opprette sitt eget brukergrensesnitt. Det vil si at hvis måten et objekts attributter er representert på, er skjult for resten av programmet, kan du ikke trekke ut disse attributtene for å bygge et brukergrensesnitt.

Merk forresten at du ikke skjuler at det finnes et attributt. (Jeg definerer Egenskap, her, som en vesentlig egenskap ved objektet.) Du vet at en Ansatt må ha en lønn eller lønnsegenskap, ellers ville det ikke være en Ansatt. (Det ville være en Person, a Frivillig, a Vagrant, eller noe annet som ikke har lønn.) Det du ikke vet - eller vil vite - er hvordan den lønnen er representert inne i objektet. Det kan være en dobbelt, a String, en skalert lang, eller binærkodet desimal. Det kan være et "syntetisk" eller "avledet" attributt, som beregnes ved kjøretid (for eksempel fra en lønnsgrad eller jobbtittel, eller ved å hente verdien fra en database). Selv om en get-metode faktisk kan skjule noen av denne implementeringsdetaljene, som vi så med Penger for eksempel kan det ikke skjule nok.

Så hvordan produserer et objekt sitt eget brukergrensesnitt og forblir vedlikeholdbart? Bare de mest forenklede objektene kan støtte noe som en vis deg selv () metode. Realistiske objekter må:

  • Vis seg selv i forskjellige formater (XML, SQL, kommaadskilte verdier, etc.).
  • Vis annerledes utsikt av seg selv (en visning kan vise alle attributtene, en annen kan bare vise en delmengde av attributtene, og en tredje kan presentere attributtene på en annen måte).
  • Vis seg ut i forskjellige miljøer (klientsiden (JKomponent) og server-til-klient (HTML), for eksempel) og håndterer både input og output i begge miljøene.

Noen av leserne av min forrige getter / setter-artikkel sprang til konklusjonen at jeg talte for at du la til metoder for objektet for å dekke alle disse mulighetene, men at "løsningen" er åpenbart tull. Ikke bare er det resulterende tungevektobjektet altfor komplisert, du må stadig endre det for å håndtere nye UI-krav. Praktisk talt kan et objekt bare ikke bygge alle mulige brukergrensesnitt for seg selv, hvis det av ingen annen grunn enn mange av disse brukergrensesnittene ikke engang ble unnfanget da klassen ble opprettet.

Bygg en løsning

Problemets løsning er å skille UI-koden fra kjernevirksomhetsobjektet ved å plassere den i en egen klasse av objekter. Det vil si at du bør dele av noe funksjonalitet som kunne være i objektet til et eget objekt helt.

Denne forgreningen av et objekts metoder vises i flere designmønstre. Du er mest sannsynlig kjent med Strategi, som brukes med de forskjellige java.awt.Container klasser for å gjøre layout. Du kan løse oppsettproblemet med en avledningsløsning: FlowLayoutPanel, GridLayoutPanel, BorderLayoutPanelosv., men det krever for mange klasser og mye duplisert kode i disse klassene. En enkelt løsning i tungvektsklasse (legge til metoder for Container som layOutAsGrid (), layOutAsFlow ()osv.) er også upraktisk fordi du ikke kan endre kildekoden for Container ganske enkelt fordi du trenger en layout som ikke støttes. I Strategimønsteret lager du en Strategi grensesnitt (LayoutManager) implementert av flere Betongstrategi klasser (FlowLayout, GridLayout, etc.). Du forteller deretter a Kontekst objekt (a Container) hvordan du gjør noe ved å sende det a Strategi gjenstand. (Du passerer en Container en LayoutManager som definerer en layoutstrategi.)

Byggmønsteret ligner på Strategi. Hovedforskjellen er at Bygger klasse implementerer en strategi for å konstruere noe (som en JKomponent eller XML-strøm som representerer et objekts tilstand). Bygger objekter bygger vanligvis produktene sine ved hjelp av en flertrinnsprosess også. Det vil si samtaler til ulike metoder for Bygger kreves for å fullføre byggeprosessen, og Bygger kjenner vanligvis ikke i hvilken rekkefølge samtalene skal ringes, eller hvor mange ganger en av metodene blir ringt. Byggherrens viktigste egenskap er at forretningsobjektet (kalt Kontekst) vet ikke nøyaktig hva Bygger objektet bygger. Mønsteret isolerer forretningsobjektet fra dets representasjon.

Den beste måten å se hvordan en enkel byggmester fungerer, er å se på en. La oss først se på Kontekst, forretningsobjektet som trenger å avsløre et brukergrensesnitt. Oppføring 1 viser en forenklet Ansatt klasse. De Ansatt har Navn, id, og lønn attributter. (Stubber for disse klassene er nederst i oppføringen, men disse stubbene er bare plassholdere for den virkelige tingen. Du kan - håper jeg - lett forestille deg hvordan disse klassene vil fungere.)

Dette bestemte Kontekst bruker det jeg tenker på som en toveis byggmester. Den klassiske Gang of Four Builder går i en retning (output), men jeg har også lagt til en Bygger at en Ansatt objektet kan bruke til å initialisere seg selv. To Bygger grensesnitt kreves. De Ansatt. Eksportør grensesnitt (liste 1, linje 8) håndterer utgangsretningen. Den definerer et grensesnitt til en Bygger objekt som konstruerer representasjonen av det nåværende objektet. De Ansatt delegerer selve brukergrensesnittkonstruksjonen til Bygger i eksport() metode (på linje 31). De Bygger er ikke passert de faktiske feltene, men bruker i stedet Strings for å gi en representasjon av disse feltene.

Oppføring 1. Ansatt: The Builder Context

 1 import java.util.Locale; 2 3 offentlig klasse Ansatt 4 {privat Navn navn; 5 private EmployeeId id; 6 private penger lønn; 7 8 offentlig grensesnitt Eksportør 9 {void addName (strengnavn); 10 void addID (String id); 11 ugyldig addSalary (streng lønn); 12} 13 14 offentlig grensesnitt Importør 15 {String giveName (); 16 streng gi ID (); 17 String supplySalary (); 18 ugyldig åpen (); 19 ugyldig lukk (); 20} 21 22 offentlig ansatt (importørbygger) 23 {builder.open (); 24 this.name = nytt navn (builder.provideName ()); 25 this.id = new EmployeeId (builder.provideID ()); 26 this.salary = new Money (builder.provideSalary (), 27 new Locale ("en", "US")); 28 byggmester.lukk (); 29} 30 31 offentlig ugyldig eksport (eksportørbygger) 32 {builder.addName (name.toString ()); 33 builder.addID (id.toString ()); 34 builder.addSalary (lønn.tilString ()); 35} 36 37 //... 38 } 39 //---------------------------------------------------------------------- 40 // Enhetstest ting 41 // 42 klasse Navn 43 {privat strengverdi; 44 offentlig navn (strengverdi) 45 {this.value = verdi; 46} 47 offentlige String toString () {returverdi; }; 48} 49 50 class EmployeeId 51 {private strengverdi; 52 offentlig EmployeeId (strengverdi) 53 {this.value = verdi; 54} 55 offentlige String toString () {returverdi; } 56} 57 58 klasse Penger 59 {privat strengverdi; 60 offentlige penger (strengverdi, lokalitet) 61 {this.value = verdi; 62} 63 offentlig String toString () {returverdi; } 64} 

La oss se på et eksempel. Følgende kode bygger figur 1s brukergrensesnitt:

Medarbeider wilma = ...; JComponentExporter uiBuilder = ny JComponentExporter (); // Lag byggmesteren wilma.export (uiBuilder); // Bygg brukergrensesnittet JComponent userInterface = uiBuilder.getJComponent (); //... someContainer.add (userInterface); 

Oppføring 2 viser kilden til JComponentExporter. Som du kan se, er all UI-relatert kode konsentrert i Betongbygger (de JComponentExporter), og Kontekst (de Ansatt) driver byggeprosessen uten å vite nøyaktig hva den bygger.

Oppføring 2. Eksport til et brukergrensesnitt på klientsiden

 1 import javax.swing. *; 2 importer java.awt. *; 3 importer java.awt.event. *; 4 5 klasse JComponentExporter implementerer Medarbeider.Exportør 6 {privat Strengnavn, id, lønn; 7 8 public void addName (String name) {this.name = name; } 9 offentlig ugyldig addID (streng-id) {this.id = id; } 10 offentlige ugyldige tilleggSalary (strenglønn) {this.salary = lønn; } 11 12 JComponent getJComponent () 13 {JComponent panel = ny JPanel (); 14 panel.setLayout (ny GridLayout (3,2)); 15 panel.add (ny JLabel ("Navn:")); 16 panel.add (ny JLabel (navn)); 17 panel.add (ny JLabel ("Medarbeider-ID:")); 18 panel.add (ny JLabel (id)); 19 panel.add (ny JLabel ("Lønn:")); 20 panel.add (ny JLabel (lønn)); 21 returpanel; 22} 23} 
$config[zx-auto] not found$config[zx-overlay] not found