Programovanie

Viac o getroch a setteroch

Je to 25 rokov starý princíp objektovo-orientovaného (OO) dizajnu, ktorý by ste nemali vystaviť implementáciu objektu žiadnym iným triedam v programe. Program je zbytočne ťažké udržiavať, keď vystavujete implementáciu, predovšetkým preto, že zmena objektu, ktorý vystavuje svoju implementáciu, vyžaduje zmeny vo všetkých triedach, ktoré objekt používajú.

Nanešťastie idiom getter / setter, ktorý si mnohí programátori myslia ako objektovo orientovaný, porušuje tento základný princíp OO v pikách. Zvážte príklad a Peniaze trieda, ktorá má a getValue () metóda, ktorá vráti „hodnotu“ v dolároch. Celý program budete mať nasledujúci kód:

dvojitá objednávkaCelkom; Suma peňazí = ...; //... orderTotal + = amount.getValue (); // orderTotal must be in dollars

Problém tohto prístupu spočíva v tom, že predchádzajúci kód vytvára veľký predpoklad o tom, ako Peniaze trieda je implementovaná (že „hodnota“ je uložená v a dvojitý). Kód, ktorý pri zmene implementácie rozbíja predpoklady implementácie. Ak napríklad potrebujete svoju aplikáciu internacionalizovať, aby podporovala iné meny ako doláre, potom getValue () nevracia nič zmysluplné. Môžete pridať a getCurrency (), ale tým by sa vytvoril celý kód okolo getValue () zavolajte oveľa komplikovanejšie, najmä ak vytrvalo využívate stratégiu getter / setter na získanie informácií potrebných na vykonanie práce. Typická (chybná) implementácia môže vyzerať takto:

Suma peňazí = ...; //... hodnota = suma.getValue (); mena = suma.getCena (); conversion = CurrencyTable.getConversionFactor (mena, USDOLLARS); celková + = hodnota * prepočet; //...

Táto zmena je príliš komplikovaná na to, aby sa dala zvládnuť automatizovaným refaktoringom. Tieto zmeny by ste navyše museli robiť všade vo svojom kóde.

Riešením tohto problému na úrovni obchodnej logiky je vykonať prácu v objekte, ktorý má informácie potrebné na vykonanie práce. Namiesto toho, aby ste extrahovali „hodnotu“, aby ste s ňou mohli vykonať nejakú externú operáciu, mali by ste mať Peniaze triedy vykonávať všetky peňažné operácie vrátane prepočtu mien. Správne štruktúrovaný objekt by s súčtom zaobchádzal takto:

Peniaze spolu = ...; Suma peňazí = ...; total.increaseBy (suma); 

The pridať () Metóda by zistila menu operandu, urobila potrebný prevod meny (čo je operácia správne) peniaze) a aktualizujte celkový počet. Ak ste na začiatku použili túto stratégiu objektu, ktorá má informáciu robí, pojem mena je možné pridať do Peniaze triedy bez akýchkoľvek zmien vyžadovaných v kóde, ktorý používa Peniaze predmety. To znamená, že práca na refinancovaní prostriedkov iba na doláre na medzinárodnú implementáciu by sa sústredila na jednom mieste: Peniaze trieda.

Problém

Väčšina programátorov nemá problém s uchopením tohto konceptu na úrovni obchodnej logiky (aj keď to môže vyžadovať určité úsilie, aby to tak bolo). Problémy sa však začnú objavovať, keď do obrazu vstúpi používateľské rozhranie (UI). Problém nie je v tom, že na vytvorenie používateľského rozhrania nemôžete použiť také techniky, aké som práve opísal, ale že veľa programátorov je uzamknutých v mentalite getra / setra, pokiaľ ide o užívateľské rozhrania. Vinu za tento problém označujem v zásade procedurálnymi nástrojmi na tvorbu kódov, ako je Visual Basic a jeho klony (vrátane nástrojov na vytváranie používateľského rozhrania Java), ktoré vás nútia do tohto procedurálneho spôsobu uvažovania / nastavenia.

(Digression: Niektorí z vás sa postavia pred predchádzajúce tvrdenie a budú kričať, že VB je založená na posvätenej architektúre Model-View-Controller (MVC), takže je tiež posvätná. Majte na pamäti, že MVC bolo vyvinuté takmer pred 30 rokmi. Na začiatku V 70. rokoch bol najväčší superpočítač porovnateľný s dnešnými pracovnými plochami. Väčšina strojov (napríklad DEC PDP-11) boli 16-bitové počítače so 64 kB pamäte a rýchlosťami hodín meranými v desiatkach megahertzov. Vaše používateľské rozhranie bolo pravdepodobne stoh diernych štítkov. Ak ste mali šťastie na videoterminál, možno ste používali systém vstupno-výstupných (I / O) konzol založených na ASCII. Za posledných 30 rokov sme sa toho veľa naučili. Dokonca Java Swing musel nahradiť MVC podobnou architektúrou „separable-model“, predovšetkým preto, že čistý MVC nedostatočne izoluje vrstvy používateľského rozhrania a modelu domény.)

Poďme si teda definovať problém v skratke:

Ak objekt nemusí zverejňovať informácie o implementácii (metódami get / set alebo iným spôsobom), je zrejmé, že objekt musí nejako vytvoriť svoje vlastné užívateľské rozhranie. To znamená, že ak je spôsob reprezentácie atribútov objektu skrytý pred zvyškom programu, nemôžete tieto atribúty extrahovať na vytvorenie používateľského rozhrania.

Mimochodom, mimochodom sa netajíte tým, že atribút existuje. (Definujem atribút, tu, ako podstatná charakteristika objektu.) Viete, že an Zamestnanec musí mať atribút plat alebo mzda, inak by to nebol Zamestnanec. (Bolo by to Osoba, a Dobrovoľník, a Vagrant, alebo niečo iné, čo nemá plat.) Čo však neviete - alebo chcete vedieť - je to, ako je tento plat znázornený vo vnútri objektu. Môže to byť a dvojitý, a String, zmenšený dlho, alebo binárne kódované desatinné miesto. Môže to byť atribút „syntetický“ alebo „odvodený“, ktorý sa počíta za behu programu (napríklad z platovej triedy alebo pracovného zaradenia alebo načítaním hodnoty z databázy). Aj keď metóda get môže skutočne skryť niektoré z týchto podrobností implementácie, ako sme videli v Peniaze nemôže sa napríklad dostatočne skryť.

Ako teda objekt vytvára svoje vlastné používateľské rozhranie a je udržiavateľný? Iba tie najjednoduchšie objekty môžu podporovať niečo ako a displayYourself () metóda. Realistické objekty musia:

  • Zobrazujú sa v rôznych formátoch (XML, SQL, hodnoty oddelené čiarkou atď.).
  • Zobraziť rôzne názory samy o sebe (jedno zobrazenie môže zobraziť všetky atribúty; iné môže zobraziť iba podmnožinu atribútov; a tretie môže prezentovať atribúty odlišným spôsobom).
  • Zobrazujú sa v rôznych prostrediach (na strane klienta (JComponent) a slúži klientovi (HTML), napríklad) a spracováva vstup aj výstup v oboch prostrediach.

Niektorí z čitateľov môjho predchádzajúceho článku o getteroch / nastavovačoch dospeli k záveru, že sa zasadzujem za to, aby ste do objektu pridali metódy, ktoré pokryjú všetky tieto možnosti, ale toto „riešenie“ je zjavne nezmyselné. Nielenže je výsledný ťažký objekt príliš komplikovaný, budete ho musieť neustále upravovať, aby ste zvládli nové požiadavky používateľského rozhrania. V skutočnosti objekt jednoducho nemôže pre seba vytvoriť všetky možné používateľské rozhrania, ak z iného dôvodu, ako bolo mnoho z týchto používateľských rozhraní, nebolo ani len vytvorených, keď bola trieda vytvorená.

Vytvorte riešenie

Riešením tohto problému je oddeliť kód používateľského rozhrania od hlavného obchodného objektu vložením do samostatnej triedy objektov. To znamená, že by ste mali oddeliť niektoré funkcionality, ktoré mohol byť v objekte úplne v samostatnom objekte.

Toto rozdvojenie metód objektu sa objavuje v niekoľkých návrhových vzoroch. S najväčšou pravdepodobnosťou poznáte stratégiu, ktorá sa používa s rôznymi nástrojmi java.awt.Container triedy urobiť rozloženie. Problém s usporiadaním by ste mohli vyriešiť derivačným riešením: FlowLayoutPanel, GridLayoutPanel, BorderLayoutPanelatď., ale to vyžaduje v týchto triedach príliš veľa tried a veľa duplikovaného kódu. Jediné riešenie triedy ťažkej váhy (pridanie metód do Kontajner Páči sa mi to layOutAsGrid (), layOutAsFlow (), atď.) je tiež nepraktické, pretože nemôžete upraviť zdrojový kód súboru Kontajner jednoducho preto, lebo potrebujete nepodporované rozloženie. Vo vzore Stratégia vytvoríte a Stratégia rozhranie (LayoutManager) implementovali viacerí Konkrétna stratégia triedy (FlowLayout, GridLayout, atď.). Potom povieš a Kontext objekt (a Kontajner) ako niečo urobiť odovzdaním a Stratégia objekt. (Miniete a Kontajner a LayoutManager ktorá definuje stratégiu rozloženia.)

Staviteľský model je podobný ako v prípade stratégie. Hlavný rozdiel je v tom, že Staviteľ trieda implementuje stratégiu na konštrukciu niečoho (ako a JComponent alebo tok XML, ktorý predstavuje stav objektu). Staviteľ objekty zvyčajne vyrábajú svoje produkty aj pomocou viacstupňového procesu. To znamená, že volá po rôznych metódach Staviteľ sú povinné na dokončenie stavebného procesu a Staviteľ zvyčajne nevie, v akom poradí sa uskutočnia hovory, ani koľkokrát sa zavolá jedna z jeho metód. Najdôležitejšou charakteristikou staviteľa je, že obchodný objekt (nazývaný Kontext) nevie presne čo Staviteľ objekt je budova. Vzor izoluje obchodný objekt od jeho znázornenia.

Najlepším spôsobom, ako zistiť, ako funguje jednoduchý staviteľ, je pozrieť sa na jeden. Najprv sa pozrime na Kontext, obchodný objekt, ktorý potrebuje vystaviť užívateľské rozhranie. Zoznam 1 ukazuje zjednodušenie Zamestnanec trieda. The Zamestnanecnázov, ida plat atribúty. (Pahýly týchto tried sú v dolnej časti zoznamu, ale tieto pahýly sú iba zástupnými symbolmi skutočnej veci. Dúfam, že si môžete ľahko predstaviť, ako by tieto triedy fungovali.)

Tento konkrétny Kontext používa to, o čom si myslím, že je obojsmerným staviteľom. Klasický Gang of Four Builder ide jedným smerom (výstupom), ale pridal som aj a Staviteľ že an Zamestnanec objekt môže použiť na svoju inicializáciu. Dva Staviteľ sú potrebné rozhrania. The Zamestnanec. Vývozca rozhranie (zoznam 1, riadok 8) spracováva smer výstupu. Definuje rozhranie k Staviteľ objekt, ktorý vytvára reprezentáciu aktuálneho objektu. The Zamestnanec deleguje skutočnú stavbu používateľského rozhrania na Staviteľ v export() metóda (na riadku 31). The Staviteľ nie je odovzdané skutočné polia, ale namiesto toho používa Strings odovzdať zastúpenie týchto polí.

Zoznam 1. Zamestnanec: Kontext Builderu

 1 import java.util.Locale; 2 3 verejná trieda Zamestnanec 4 {súkromné ​​meno meno; 5 súkromné ​​ID zamestnanca; 6 plat zo súkromných peňazí; 7 8 verejné rozhranie Vývozca 9 {void addName (názov reťazca); 10 void addID (ID reťazca); 11 neplatných addSalary (reťazcový plat); 12} 13 14 verejné rozhranie Importér 15 {String provideName (); 16 Reťazec provideID (); 17 Reťazec provideSalary (); 18 prázdne otvorené (); 19 void close (); 20} 21 22 verejných zamestnancov (tvorca dovozcov) 23 {builder.open (); 24 this.name = new Name (builder.provideName ()); 25 this.id = new EmployeeId (builder.provideID ()); 26 this.salary = new Money (builder.provideSalary (), 27 new Locale ("en", "US")); 28 builder.close (); 29} 30 31 public void export (buildér exportéra) 32 {builder.addName (name.toString ()); 33 builder.addID (id.toString ()); 34 builder.addSalary (plat.toString ()); 35} 36 37 //... 38 } 39 //---------------------------------------------------------------------- 40 // Testovacie jednotky 41 // 42 názov triedy 43 {súkromná hodnota reťazca; 44 verejné meno (hodnota reťazca) 45 {this.value = hodnota; 46} 47 public String toString () {návratová hodnota; }; 48} 49 50 trieda EmployeeId 51 {private String value; 52 public EmployeeId (hodnota reťazca) 53 {this.value = hodnota; 54} 55 public String toString () {návratová hodnota; } 56} 57 58 trieda Peniaze 59 {súkromná hodnota reťazca; 60 verejné peniaze (hodnota reťazca, miestne nastavenie) 61 {this.value = hodnota; 62} 63 public String toString () {návratová hodnota; } 64} 

Pozrime sa na príklad. Nasledujúci kód vytvára používateľské rozhranie obrázku 1:

Zamestnanec wilma = ...; JComponentExporter uiBuilder = nový JComponentExporter (); // Vytvorte staviteľ wilma.export (uiBuilder); // Vytvorenie používateľského rozhrania JComponent userInterface = uiBuilder.getJComponent (); //... someContainer.add (userInterface); 

Výpis 2 zobrazuje zdroj pre JComponentExporter. Ako vidíte, všetok kód súvisiaci s UI je sústredený v Staviteľ betónu (the JComponentExporter) a Kontext (the Zamestnanec) riadi proces vytvárania bez toho, aby vedel, čo presne sa vytvára.

Zoznam 2. Export do používateľského rozhrania na strane klienta

 1 import javax.swing. *; 2 import java.awt. *; 3 import java.awt.event. *; 4 5 trieda JComponentExporter implementuje Employee.Exporter 6 {private String name, id, plate; 7 8 public void addName (názov reťazca) {this.name = name; } 9 public void addID (ID reťazca) {this.id = id; } 10 public void addSalary (reťazcový plat) {this.salary = plat; } 11 12 JComponent getJComponent () 13 {panel JComponent = nový JPanel (); 14 panel.setLayout (nový GridLayout (3,2)); 15 panel.add (nový JLabel ("Meno:")); 16 panel.add (nový JLabel (meno)); 17 panel.add (nový JLabel („ID zamestnanca:“)); 18 panel.add (nový JLabel (id)); 19 panel.add (nový JLabel ("Plat:")); 20 panel.add (nový JLabel (plat)); 21 vratný panel; 22} 23} 
$config[zx-auto] not found$config[zx-overlay] not found