Programmering

Mere om getters og setters

Det er et 25 år gammelt princip med objektorienteret (OO) design, at du ikke skal udsætte et objekts implementering for andre klasser i programmet. Programmet er unødvendigt vanskeligt at vedligeholde, når du udsætter implementeringen, primært fordi ændring af et objekt, der udsætter dets implementering, kræver ændringer til alle de klasser, der bruger objektet.

Desværre er det getter / setter-idiom, som mange programmører tænker på som objektorienteret, i strid med dette grundlæggende OO-princip. Overvej eksemplet med en Penge klasse, der har en getValue () metode på den, der returnerer "værdien" i dollars. Du vil have kode som følgende over hele dit program:

dobbelt ordreTotal; Pengebeløb = ...; //... orderTotal + = beløb.getValue (); // orderTotal skal være i dollars

Problemet med denne tilgang er, at den foregående kode giver en stor antagelse om, hvordan Penge klasse implementeres (at "værdien" er gemt i en dobbelt). Kode, der gør antagelser om implementering brudt, når implementeringen ændres. Hvis du for eksempel skal internationalisere din ansøgning for at understøtte andre valutaer end dollars, så getValue () returnerer intet meningsfuldt. Du kan tilføje en getCurrency (), men det ville gøre al koden omkring getValue () ring meget mere kompliceret, især hvis du fortsætter med at bruge getter / setter-strategien for at få de oplysninger, du har brug for til at udføre arbejdet. En typisk (mangelfuld) implementering kan se sådan ud:

Pengebeløb = ...; //... værdi = beløb.getValue (); valuta = beløb.getCurrency (); conversion = CurrencyTable.getConversionFactor (valuta, USDOLLARS); total + = værdi * konvertering //...

Denne ændring er for kompliceret til at blive håndteret af automatiseret refactoring. Desuden skal du foretage denne slags ændringer overalt i din kode.

Den forretningslogiske løsning på dette problem er at udføre arbejdet i det objekt, der har de nødvendige oplysninger til at udføre arbejdet. I stedet for at udtrække "værdien" for at udføre nogle eksterne operationer på den, skal du have Penge klasse udfører alle de penge-relaterede operationer, herunder valutakonvertering. Et korrekt struktureret objekt vil håndtere det samlede således:

Penge i alt = ...; Pengebeløb = ...; total.increaseBy (beløb); 

Det tilføje() metoden ville finde ud af operandens valuta, foretage enhver nødvendig valutakonvertering (som korrekt er en operation på penge), og opdater summen. Hvis du til at begynde med brugte denne objekt-der-har-informationen-gør-arbejdstrategien, forestillingen om betalingsmiddel kunne føjes til Penge klasse uden ændringer krævet i den kode, der bruger Penge genstande. Arbejdet med at omlægge kun dollars til en international implementering vil være koncentreret på et enkelt sted: Penge klasse.

Problemet

De fleste programmører har ingen problemer med at forstå dette koncept på forretningslogisk niveau (selvom det kan kræve en vis indsats at konsekvent tænke sådan). Problemer begynder dog at opstå, når brugergrænsefladen (UI) kommer ind i billedet. Problemet er ikke, at du ikke kan anvende teknikker som den, jeg lige har beskrevet for at opbygge et brugergrænseflade, men at mange programmører er låst fast i en getter / setter-mentalitet, når det kommer til brugergrænseflader. Jeg bebrejder dette problem med fundamentalt proceduremæssige kodekonstruktionsværktøjer som Visual Basic og dets kloner (inklusive Java UI-bygherrer), der tvinger dig ind i denne proceduremæssige, getter / setter måde at tænke på.

(Digression: Nogle af jer vil svigte af den foregående udsagn og skrige, at VB er baseret på den hellige Model-View-Controller (MVC) -arkitektur, det er også hellig. Husk, at MVC blev udviklet for næsten 30 år siden. I begyndelsen I 1970'erne var den største supercomputer på niveau med nutidens desktops. De fleste maskiner (såsom DEC PDP-11) var 16-bit computere med 64 KB hukommelse og klokkehastigheder målt i snesevis af megahertz. Din brugergrænseflade var sandsynligvis en stak med stansede kort. Hvis du var heldig nok til at have en videoterminal, har du muligvis brugt et ASCII-baseret konsolindgang / output-system (I / O). Vi har lært meget i de sidste 30 år. Selv Java Swing måtte erstatte MVC med en lignende "adskillelig model" -arkitektur, primært fordi ren MVC ikke tilstrækkeligt isolerer UI- og domænemodellagene.)

Så lad os definere problemet i en nøddeskal:

Hvis et objekt muligvis ikke udsætter implementeringsoplysninger (gennem get / set-metoder eller på nogen anden måde), er det grund til at et objekt på en eller anden måde skal oprette sin egen brugergrænseflade. Det vil sige, at hvis den måde, et objekts attributter er repræsenteret på, er skjult for resten af ​​programmet, kan du ikke udtrække disse attributter for at opbygge et brugergrænseflade.

Bemærk forresten, at du ikke skjuler, at der findes en attribut. (Jeg definerer attribut, her, som et væsentligt kendetegn ved objektet.) Du ved, at et Medarbejder skal have en løn- eller lønegenskab, ellers ville det ikke være en Medarbejder. (Det ville være en Person, a Frivillig, a Vagranteller noget andet, der ikke har løn.) Hvad du ikke kender - eller ønsker at vide - er, hvordan lønnen er repræsenteret inde i objektet. Det kunne være en dobbelt, a Snor, en skaleret langeller binært kodet decimal. Det kan være en "syntetisk" eller "afledt" attribut, der beregnes ved kørsel (f.eks. Fra en lønklasse eller jobtitel eller ved at hente værdien fra en database). Selvom en get-metode faktisk kan skjule nogle af denne implementeringsdetalje, som vi så med Penge for eksempel kan det ikke skjule nok.

Så hvordan producerer et objekt sit eget brugergrænseflade og forbliver vedligeholdeligt? Kun de mest forenklede objekter kan understøtte noget som en vis dig selv () metode. Realistiske objekter skal:

  • Vis sig selv i forskellige formater (XML, SQL, komma-adskilte værdier osv.).
  • Vis anderledes synspunkter af sig selv (en visning viser muligvis alle attributter, en anden viser muligvis kun en delmængde af attributterne, og en tredje viser muligvis attributterne på en anden måde).
  • Vis sig selv i forskellige miljøer (klientside (JKomponent) og serveret til klient (HTML), for eksempel) og håndterer både input og output i begge miljøer.

Nogle af læserne af min tidligere getter / setter-artikel sprang til den konklusion, at jeg foreslog, at du tilføjede metoder til objektet for at dække alle disse muligheder, men at "løsningen" er åbenlyst meningsløs. Ikke alene er den resulterende tunge genstand alt for kompliceret, du bliver konstant nødt til at ændre den for at håndtere nye UI-krav. Praktisk kan et objekt bare ikke opbygge alle mulige brugergrænseflader til sig selv, hvis der ikke af nogen anden grund end mange af disse brugergrænseflader ikke engang blev udtænkt, da klassen blev oprettet.

Byg en løsning

Problemets løsning er at adskille UI-koden fra kerneforretningsobjektet ved at placere den i en separat klasse af objekter. Det vil sige, du skal opdele nogle funktioner, der kunne være helt i objektet til et separat objekt.

Denne fordeling af et objekts metoder vises i flere designmønstre. Du er højst sandsynligt fortrolig med Strategi, som bruges med de forskellige java.awt.Container klasser at lave layout. Du kan løse layoutproblemet med en afledningsløsning: FlowLayoutPanel, GridLayoutPanel, BorderLayoutPanelosv., men det foreskriver for mange klasser og en masse duplikeret kode i disse klasser. En enkelt tungvægtsklasse-løsning (tilføjelse af metoder til Beholder synes godt om layOutAsGrid (), layOutAsFlow ()osv.) er også upraktisk, fordi du ikke kan ændre kildekoden til Beholder simpelthen fordi du har brug for et ikke-understøttet layout. I strategimønsteret opretter du en Strategi interface (LayoutManager) implementeret af flere Konkret strategi klasser (FlowLayout, GridLayout, etc.). Du fortæller derefter en Sammenhæng objekt (a Beholder) hvordan man gør noget ved at videregive det a Strategi objekt. (Du passerer en Beholder -en LayoutManager der definerer en layoutstrategi.)

Builder-mønsteret ligner strategi. Den største forskel er, at Bygger klasse implementerer en strategi til at konstruere noget (som en JKomponent eller XML-stream, der repræsenterer et objekts tilstand). Bygger objekter bygger typisk deres produkter ved hjælp af en flertrinsproces også. Det vil sige opkald til forskellige metoder til Bygger kræves for at gennemføre byggeprocessen, og Bygger kender typisk ikke rækkefølgen, som opkaldene foretages i, eller hvor mange gange en af ​​dens metoder vil blive kaldt. Byggerens vigtigste egenskab er, at forretningsobjektet (kaldet Sammenhæng) ved ikke nøjagtigt hvad Bygger objekt bygger. Mønsteret isolerer forretningsobjektet fra dets repræsentation.

Den bedste måde at se, hvordan en simpel bygherre fungerer, er at se på en. Lad os først se på Sammenhæng, det forretningsobjekt, der skal eksponere en brugergrænseflade. Liste 1 viser en forenklet Medarbejder klasse. Det Medarbejder har navn, idog løn egenskaber. (Stubber til disse klasser er nederst på listen, men disse stubber er bare pladsholdere for den rigtige ting. Du kan - håber jeg - let forestille mig, hvordan disse klasser ville fungere.)

Denne særlige Sammenhæng bruger det, jeg tænker på som en tovejsbygger. Den klassiske Gang of Four Builder går i en retning (output), men jeg har også tilføjet en Bygger at en Medarbejder objekt kan bruge til at initialisere sig selv. To Bygger interface kræves. Det Medarbejder. Eksportør interface (Listing 1, line 8) håndterer outputretningen. Det definerer en grænseflade til en Bygger objekt, der konstruerer repræsentationen af ​​det aktuelle objekt. Det Medarbejder delegerer den faktiske UI-konstruktion til Bygger i eksport() metode (på linje 31). Det Bygger passeres ikke de faktiske felter, men bruger i stedet Snors til at bestå en repræsentation af disse felter.

Notering 1. Medarbejder: The Builder Context

 1 import java.util.Locale; 2 3 offentlig klasse Medarbejder 4 {privat Navn navn; 5 private medarbejder-id; 6 private penge løn; 7 8 offentlig grænseflade Eksportør 9 {void addName (strengnavn); 10 ugyldigt addID (streng-id); 11 ugyldigt addSalary (streng løn); 12} 13 14 offentlig grænseflade Importør 15 {String giveName (); 16 streng giveID (); 17 streng giveSalary (); 18 ugyldigt åbent (); 19 ugyldigt tæt (); 20} 21 22 offentlig ansat (importørbygger) 23 {builder.open (); 24 this.name = nyt navn (builder.provideName ()); 25 this.id = ny EmployeeId (builder.provideID ()); 26 this.salary = new Money (builder.provideSalary (), 27 new Locale ("en", "US")); 28 builder.close (); 29} 30 31 offentlig ugyldig eksport (eksportørbygger) 32 {builder.addName (name.toString ()); 33 builder.addID (id.toString ()); 34 builder.addSalary (løn.tilString ()); 35} 36 37 //... 38 } 39 //---------------------------------------------------------------------- 40 // Enhedstest ting 41 // 42 klasse Navn 43 {privat strengværdi; 44 offentligt navn (strengværdi) 45 {this.value = værdi; 46} 47 offentlige String toString () {returværdi; }; 48} 49 50 klasse EmployeeId 51 {privat strengværdi; 52 offentlig EmployeeId (strengværdi) 53 {this.value = værdi; 54} 55 offentlige String toString () {returværdi; } 56} 57 58 klassepenge 59 {privat strengværdi; 60 offentlige penge (strengværdi, lokalitet) 61 {this.value = værdi; 62} 63 offentlige String toString () {returværdi; } 64} 

Lad os se på et eksempel. Følgende kode bygger figur 1's brugergrænseflade:

Medarbejder wilma = ...; JComponentExporter uiBuilder = ny JComponentExporter (); // Opret bygherren wilma.export (uiBuilder); // Byg brugergrænsefladen JComponent userInterface = uiBuilder.getJComponent (); //... someContainer.add (userInterface); 

Liste 2 viser kilden til JComponentExporter. Som du kan se, er al UI-relateret kode koncentreret i Betonbygger (det JComponentExporter), og Sammenhæng (det Medarbejder) driver byggeprocessen uden at vide nøjagtigt, hvad den bygger.

Fortegnelse 2. Eksport til et brugergrænseflade på klientsiden

 1 import javax.swing. *; 2 importer java.awt. *; 3 import java.awt.event. *; 4 5 klasse JComponentExporter implementerer medarbejder.Exporter 6 {privat Stringnavn, id, løn; 7 8 public void addName (String name) {this.name = name; } 9 offentlige ugyldige addID (streng-id) {this.id = id; } 10 offentlige ugyldige addSalary (streng løn) {this.salary = løn; } 11 12 JComponent getJComponent () 13 {JComponent panel = ny JPanel (); 14 panel.setLayout (nyt GridLayout (3,2)); 15 panel.add (nyt JLabel ("Navn:")); 16 panel.add (nyt JLabel (navn)); 17 panel.add (nyt JLabel ("Medarbejder-ID:")); 18 panel.add (nyt JLabel (id)); 19 panel.add (nyt JLabel ("Løn:")); 20 panel.add (nyt JLabel (løn)); 21 returpanel; 22} 23}