Programiranje

Več o geterjih in seterjih

To je 25-letno načelo objektno usmerjenega (OO) oblikovanja, da izvedbe predmeta ne bi smeli izpostavljati nobenim drugim razredom v programu. Program je po nepotrebnem težko vzdrževati, ko razkrijete izvedbo, predvsem zato, ker spreminjanje predmeta, ki izpostavi njegovo izvajanje, nalaga spremembe vsem razredom, ki uporabljajo predmet.

Na žalost idiom getter / setter, za katerega mnogi programerji menijo, da je objektno usmerjen, v bistvu krši to temeljno načelo OO. Poglejmo primer a Denar razred, ki ima getValue () metoda, ki vrne "vrednost" v dolarjih. V celotnem programu boste imeli naslednjo kodo:

dvojni redTotal; Denarni znesek = ...; //... orderTotal + = amount.getValue (); // orderTotal mora biti v dolarjih

Težava tega pristopa je, da prejšnja koda veliko domneva o tem, kako Denar razred (da je "vrednost" shranjena v dvojno). Koda, ki naredi predpostavke izvedbe, se po spremembi izvedbe zlomi. Če morate na primer svojo aplikacijo internacionalizirati, da podpira valute, ki niso dolarji, potem getValue () vrne nič smiselnega. Lahko dodate getCurrency (), toda zaradi tega bi bila vsa koda, ki obdaja getValue () pokličite veliko bolj zapleteno, še posebej, če vztrajate pri uporabi strategije pridobivanja / nastavitve, da dobite informacije, ki jih potrebujete za delo. Tipična (napačna) izvedba bi lahko izgledala takole:

Denarni znesek = ...; //... vrednost = znesek.getValue (); currency = znesek.getCurrency (); conversion = CurrencyTable.getConversionFactor (valuta, USDOLLARS); pretvorba skupaj + = vrednost *; //...

Ta sprememba je preveč zapletena, da bi jo bilo mogoče obravnavati z avtomatiziranim preoblikovanjem. Poleg tega bi morali tovrstne spremembe narediti povsod v svoji kodi.

Rešitev te težave na ravni poslovne logike je opraviti delo v objektu, ki ima informacije, potrebne za njegovo izvedbo. Namesto da izvlečete "vrednost" in izvedete zunanjo operacijo, bi morali imeti Denar razred opravlja vse denarne operacije, vključno s pretvorbo valut. Pravilno strukturiran objekt bi obravnaval celotno sestavo takole:

Denar skupaj = ...; Denarni znesek = ...; total.increaseBy (znesek); 

The dodaj () metoda bi ugotovila valuto operanda, opravila vse potrebne pretvorbe valut (kar je pravilno operacija na denarja) in posodobite skupni znesek. Če ste za začetek uporabili to strategijo objekt-ki-ima-informacije-opravi-delo, pojem valuta je mogoče dodati v Denar razred brez kakršnih koli sprememb v kodi, ki uporablja Denar predmetov. To pomeni, da bi bilo delo za predelavo samo dolarjev na mednarodno izvedbo osredotočeno na eno samo mesto: Denar razred.

Težava

Večina programerjev nima težav z dojemanjem tega koncepta na ravni poslovne logike (čeprav si je treba nekaj časa prizadevati za dosledno razmišljanje). Težave pa se začnejo pojavljati, ko v sliko vstopi uporabniški vmesnik (UI). Težava ni v tem, da za izdelavo uporabniškega vmesnika ne morete uporabiti tehnik, kot sem jo pravkar opisal, ampak v tem, da je veliko programerjev, ko gre za uporabniške vmesnike, zaklenjeno v miselnost getter / setter. To težavo krivim za v bistvu postopkovna orodja za konstrukcijo kode, kot je Visual Basic in njegovi kloni (vključno z graditelji uporabniškega vmesnika Java), ki vas prisilijo v to proceduralno, pridobivalno / nastavitveno razmišljanje.

(Digresija: Nekateri se boste strinjali s prejšnjo izjavo in kričali, da VB temelji na posvečeni arhitekturi Model-View-Controller (MVC), prav tako nenavadno. Upoštevajte, da je bil MVC razvit pred skoraj 30 leti. V začetku V sedemdesetih letih je bil največji superračunalnik enak današnjim namizjem. Večina strojev (na primer DEC PDP-11) je bila 16-bitnih računalnikov s 64 KB pomnilnika in hitrostjo, merjeno v deset megahercih. Vaš uporabniški vmesnik je bil verjetno kup bušenih kart. Če ste imeli srečo, da ste imeli video terminal, ste morda uporabljali sistem vhodno / izhodnih (I / O) konzol na osnovi ASCII. V zadnjih 30 letih smo se veliko naučili. Java Swing je moral zamenjati MVC s podobno arhitekturo "ločljivega modela", predvsem zato, ker čisti MVC ne izolira dovolj uporabniškega vmesnika in plasti modela domene.)

Torej, na kratko definirajmo težavo:

Če objekt ne sme izpostaviti informacij o izvedbi (z metodami get / set ali na kakršen koli drug način), potem je razumljivo, da mora objekt nekako ustvariti svoj uporabniški vmesnik. To pomeni, da če je način, kako so predstavljeni atributi predmeta, skrit pred ostalim programom, potem teh atributov ne morete izvleči, da bi ustvarili uporabniški vmesnik.

Mimogrede, upoštevajte, da ne skrivate dejstva, da atribut obstaja. (Določam atribut, tukaj, kot bistvena značilnost predmeta.) Veste, da je an Zaposleni mora imeti atribut plače ali plače, sicer ne bi bil Zaposleni. (Bilo bi Oseba, a Prostovoljec, a Potepuškiali kaj drugega, kar nima plače.) Česar ne veste - ali želite vedeti - je, kako je ta plača predstavljena znotraj predmeta. Lahko bi bilo dvojno, a Vrvica, pomanjšan dolgaali binarno kodirano decimalno mesto. Lahko gre za sintetični ali izpeljani atribut, ki se izračuna med izvajanjem (na primer iz plačnega razreda ali naslova delovnega mesta ali z pridobivanjem vrednosti iz baze podatkov). Čeprav metoda get dejansko lahko skrije nekatere podrobnosti o izvedbi, kot smo videli pri Denar na primer ne more skriti dovolj.

Torej, kako objekt ustvari svoj uporabniški vmesnik in ga je mogoče vzdrževati? Samo najbolj poenostavljeni predmeti lahko podpirajo nekaj takega kot displayYourself () metoda. Realistični predmeti morajo:

  • Prikazujejo se v različnih oblikah (XML, SQL, vrednosti, ločene z vejico itd.).
  • Prikaz drugačen pogledi sami (en pogled bi lahko prikazal vse atribute; drugi bi lahko prikazal le podnabor atributov; tretji pa bi atribute lahko prikazal na drugačen način).
  • Prikazujejo se v različnih okoljih (na strani odjemalca (JComponent) in na primer odjemalcu (HTML)) in v obeh okoljih obdelujejo vhodne in izhodne podatke.

Nekateri bralci mojega prejšnjega članka o getterju / setterju so skočili do zaključka, da se zavzemam za to, da predmetu dodate metode, ki bodo pokrivale vse te možnosti, vendar je ta "rešitev" očitno nesmiselna. Nastali težki predmet je ne samo preveč zapleten, temveč ga boste morali nenehno spreminjati, da bo lahko izpolnjeval nove zahteve glede uporabniškega vmesnika. Objekt praktično ne more zgraditi vseh mogočih uporabniških vmesnikov zase, če le zaradi katerega od teh uporabniških vmesnikov niti niso bili zasnovani, ko je bil razred ustvarjen.

Zgradite rešitev

Rešitev te težave je ločiti kodo uporabniškega vmesnika od osnovnega poslovnega predmeta, tako da jo postavite v ločen razred predmetov. To pomeni, da bi morali ločiti nekaj funkcij, ki lahko v celoti v ločeni predmet.

Ta razcepljenost metod predmeta se pojavlja v več vzorcih oblikovanja. Najverjetneje poznate strategijo, ki se uporablja pri različnih java.awt.Container razredi narediti postavitev. Problem postavitve bi lahko rešili z izpeljavo: FlowLayoutPanel, GridLayoutPanel, BorderLayoutPanelitd., vendar to zahteva preveč razredov in veliko podvojenih kod v teh razredih. Ena sama rešitev v težkem razredu (dodajanje metod v Zabojnik všeč layOutAsGrid (), layOutAsFlow ()itd.) je tudi nepraktično, ker ne morete spremeniti izvorne kode za Zabojnik preprosto zato, ker potrebujete nepodprto postavitev. V vzorcu strategije ustvarite a Strategija vmesnik (LayoutManager) izvaja več Konkretna strategija razredi (FlowLayout, GridLayoutitd.). Nato poveš a Kontekst predmet (a Zabojnik) kako nekaj narediti tako, da ga prenesete a Strategija predmet. (Greš mimo Zabojnik a LayoutManager ki opredeljuje strategijo postavitve.)

Vzorec Builder je podoben strategiji. Glavna razlika je v tem, da Graditelj razred izvaja strategijo za konstruiranje nečesa (kot je JComponent ali tok XML, ki predstavlja stanje predmeta). Graditelj predmeti običajno gradijo svoje izdelke tudi z večstopenjskim postopkom. To pomeni, da poziva k različnim metodam Graditelj so potrebni za dokončanje gradbenega postopka, in Graditelj običajno ne ve, v kakšnem vrstnem redu bodo opravljeni klici ali kolikokrat bo klicana ena od njegovih metod. Najpomembnejša značilnost graditelja je, da poslovni objekt (imenovan Kontekst) ne ve natančno, kaj Graditelj objekt gradi. Vzorec izolira poslovni objekt od njegove predstavitve.

Najboljši način, kako ugotoviti, kako deluje preprost graditelj, je, da ga pogledate. Najprej si oglejmo Kontekst, poslovni objekt, ki mora izpostaviti uporabniški vmesnik. Seznam 1 prikazuje poenostavljeno Zaposleni razred. The Zaposleni ima ime, id, in plača lastnosti. (Študije za te razrede so na dnu seznama, vendar so te škrbine le ograde za resnično. Lahko si - upam - enostavno predstavljate, kako bi ti razredi delovali.)

To še posebej Kontekst uporablja tisto, kar mislim kot dvosmerni graditelj. Klasična Gang of Four Builder gre v eno smer (izhod), dodal pa sem tudi Graditelj da je Zaposleni objekt lahko uporabi za samoinicializacijo. Dva Graditelj vmesniki. The Zaposlen.Izvoznik vmesnik (seznam 1, vrstica 8) obravnava izhodno smer. Določa vmesnik za Graditelj objekt, ki gradi predstavitev trenutnega predmeta. The Zaposleni prenese dejansko konstrukcijo uporabniškega vmesnika na Graditelj v izvoz () (na vrstici 31). The Graditelj ni preneseno dejansko polje, ampak namesto tega uporablja Vrvicas, da posreduje predstavitev teh polj.

Seznam 1. Zaposleni: Kontekst graditelja

 1 uvoz java.util.Locale; 2 3 javni razred Zaposleni 4 {zasebno ime; 5 zasebni ID zaposlenega ID; 6 zasebna denarna plača; 7 8 javni vmesnik Izvoznik 9 {void addName (ime niza); 10 void addID (ID niza); 11 void addSalary (nizka plača); 12} 13 14 javni vmesnik Uvoznik 15 {String provideName (); 16 String provideID (); 17 String provideSalary (); 18 praznina odprta (); 19 void close (); 20} 21 22 javni uslužbenec (graditelj uvoznikov) 23 {builder.open (); 24 this.name = novo ime (builder.provideName ()); 25 this.id = new EmployeeId (builder.provideID ()); 26 this.salary = nov denar (builder.provideSalary (), 27 novih lokacij ("en", "US")); 28 builder.close (); 29} 30 31 javni prazninski izvoz (graditelj izvoznikov) 32 {builder.addName (name.toString ()); 33 builder.addID (id.toString ()); 34 builder.addSalary (pay.toString ()); 35} 36 37 //... 38 } 39 //---------------------------------------------------------------------- 40 // Enote za preskus 41 // 42 razred Ime 43 {vrednost zasebnega niza; 44 javno ime (vrednost niza) 45 {this.value = vrednost; 46} 47 javni String toString () {vrnjena vrednost; }; 48} 49 50 class EmployeeId 51 {private String value; 52 public EmployeeId (String value) 53 {this.value = value; 54} 55 javni String toString () {vrnjena vrednost; } 56} 57 58 razred Money 59 {private String value; 60 javni denar (vrednost niza, lokalizacija) 61 {this.value = vrednost; 62} 63 javni String toString () {vrnjena vrednost; } 64} 

Oglejmo si primer. Naslednja koda gradi uporabniški vmesnik slike 1:

Zaposleni wilma = ...; JComponentExporter uiBuilder = nov JComponentExporter (); // Ustvari graditelja wilma.export (uiBuilder); // Izdelava uporabniškega vmesnika JComponent userInterface = uiBuilder.getJComponent (); //... someContainer.add (userInterface); 

Seznam 2 prikazuje vir za JComponentExporter. Kot lahko vidite, je vsa koda, povezana z uporabniškim vmesnikom, koncentrirana v Betonski graditelj ( JComponentExporter), in Kontekst ( Zaposleni) poganja postopek gradnje, ne da bi natančno vedel, kaj gradi.

Seznam 2. Izvoz v uporabniški vmesnik na strani odjemalca

 1 uvoz javax.swing. *; 2 uvoz java.awt. *; 3 uvoz java.awt.event. *; 4 5 razred JComponentExporter izvaja Employee.Exporter 6 {ime zasebnega niza, id, plača; 7 8 javna praznina addName (ime niza) {this.name = name; } 9 javni void addID (ID niza) {this.id = id; } 10 javnih neveljavnih addSalary (niz plače) {this.salary = plača; } 11 12 JComponent getJComponent () 13 {Plošča JComponent = new JPanel (); 14 panel.setLayout (novo GridLayout (3,2)); 15 panel.add (nov JLabel ("Ime:")); 16 panel.add (nov JLabel (ime)); 17 panel.add (novo JLabel ("ID zaposlenega:")); 18 panel.add (nov JLabel (id)); 19 panel.add (nova JLabel ("Plača:")); 20 panel.add (nova JLabel (plača)); 21 povratna plošča; 22} 23} 
$config[zx-auto] not found$config[zx-overlay] not found