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
, BorderLayoutPanel
atď., 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 Zamestnanec
má názov
, id
a 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 String
s 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}