Programovanie

Tip Java 75: Používajte vnorené triedy pre lepšiu organizáciu

Typický subsystém v aplikácii Java pozostáva zo sady spolupracujúcich tried a rozhraní, z ktorých každé plní konkrétnu rolu. Niektoré z týchto tried a rozhraní sú zmysluplné iba v kontexte iných tried alebo rozhraní.

Navrhovanie kontextovo závislých tried ako vnorených tried na najvyššej úrovni (skrátene vnorených tried) uzavretých triedou slúžiacou kontextu robí túto závislosť jasnejšou. Okrem toho použitie vnorených tried uľahčuje rozpoznávanie spolupráce, zabraňuje znečisteniu menného priestoru a znižuje počet zdrojových súborov.

(Celý zdrojový kód tohto tipu si môžete stiahnuť vo formáte zip v časti Zdroje.)

Vnorené triedy vs. vnútorné triedy

Vnorené triedy sú jednoducho statické vnútorné triedy. Rozdiel medzi vnorenými triedami a vnútornými triedami je rovnaký ako rozdiel medzi statickými a nestatickými členmi triedy: vnorené triedy sú spojené so samotnou uzatváracou triedou, zatiaľ čo vnútorné triedy sú spojené s objektom obklopujúcej triedy.

Z tohto dôvodu objekty vnútornej triedy vyžadujú objekt obklopujúcej triedy, zatiaľ čo vnorené objekty triedy nie. Vnorené triedy sa preto správajú rovnako ako triedy najvyššej úrovne a pomocou priloženej triedy poskytujú organizáciu podobnú balíku. Vnorené triedy majú navyše prístup ku všetkým členom nasledujúcej triedy.

Motivácia

Zvážte typický subsystém Java, napríklad komponent Swing, ktorý používa návrhový vzor Model-View-Controller (MVC). Objekty udalosti zapuzdrujú oznámenia o zmene z modelu. Zobrazenia registrujú záujem o rôzne udalosti pridaním poslucháčov k základnému modelu komponentu. Model upozorňuje svojich divákov na zmeny vo svojom vlastnom stave doručením týchto objektov udalostí svojim registrovaným poslucháčom. Často sú tieto typy poslucháčov a udalostí špecifické pre typ modelu, a preto dávajú zmysel iba v kontexte typu modelu. Pretože každý z týchto typov poslucháčov a udalostí musí byť verejne prístupný, musí byť každý vo svojom zdrojovom súbore. V tejto situácii, pokiaľ sa nepoužije nejaká konvencia kódovania, je ťažké rozpoznať spojenie medzi týmito typmi. Pre každú skupinu je samozrejme možné použiť samostatný balík na znázornenie spojenia, ale výsledkom je veľké množstvo balíkov.

Ak implementujeme typy poslucháčov a udalostí ako vnorené typy modelového rozhrania, spojku urobíme zrejmou. S týmito vnorenými typmi môžeme použiť akýkoľvek požadovaný modifikátor prístupu, vrátane public. Pretože vnorené typy používajú priložené rozhranie ako priestor názvov, zvyšok systému ich označuje ako ., vyhnúť sa znečisteniu priestoru mien v tomto balíku. Zdrojový súbor pre modelové rozhranie má všetky podporné typy, čo uľahčuje vývoj a údržbu.

Pred: Príklad bez vnorených tried

Ako príklad vyvinieme jednoduchú súčasť, Bridlica, ktorej úlohou je kresliť tvary. Rovnako ako komponenty Swing, aj my používame návrhový vzor MVC. Model, SlateModel, slúži ako úložisko tvarov. SlateModelListeners prihlásite sa na odber zmien v modeli. Model upozorňuje svojich poslucháčov odosielaním udalostí typu SlateModelEvent. V tomto príklade potrebujeme tri zdrojové súbory, jeden pre každú triedu:

// SlateModel.java import java.awt.Shape; verejné rozhranie SlateModel {// Správa poslucháčov public void addSlateModelListener (SlateModelListener l); public void removeSlateModelListener (SlateModelListener l); // Správa úložiska tvarov, pre zobrazenia je potrebné upozornenie public void addShape (Shape s); public void removeShape (Tvary s); public void removeAllShapes (); // Tvarové úložisko iba na čítanie public int getShapeCount (); public Shape getShapeAtIndex (int index); } 
// SlateModelListener.java import java.util.EventListener; verejné rozhranie SlateModelListener rozširuje EventListener {public void slateChanged (udalosť SlateModelEvent); } 
// SlateModelEvent.java import java.util.EventObject; public class SlateModelEvent rozširuje EventObject {public SlateModelEvent (model SlateModel) {super (model); }} 

(Zdrojový kód pre DefaultSlateModel, predvolená implementácia tohto modelu, je v súbore pred / DefaultSlateModel.java.)

Ďalej upriamime svoju pozornosť na Bridlica, pohľad na tento model, ktorý postúpi jeho maliarsku úlohu delegátovi používateľského rozhrania, SlateUI:

// Slate.java import javax.swing.JComponent; public class Slate extends JComponent implements SlateModelListener {private SlateModel _model; verejná bridlica (model SlateModel) {_model = model; _model.addSlateModelListener (toto); setOpaque (true); setUI (nové SlateUI ()); } public Slate () {this (new DefaultSlateModel ()); } public SlateModel getModel () {return _model; } // Implementácia poslucháča public void slateChanged (udalosť SlateModelEvent) {repaint (); }} 

Nakoniec SlateUI, vizuálna súčasť grafického používateľského rozhrania:

// SlateUI.java import java.awt. *; import javax.swing.JComponent; import javax.swing.plaf.ComponentUI; public class SlateUI extends ComponentUI {public void paint (Graphics g, JComponent c) {SlateModel model = ((Slate) c) .getModel (); g.setColor (c.getForeground ()); Graphics2D g2D = (Graphics2D) g; pre (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}} 

Po: Upravený príklad využívajúci vnorené triedy

Štruktúra triedy vo vyššie uvedenom príklade neukazuje vzťah medzi triedami. Aby sme to zmiernili, použili sme konvenciu pomenovania, ktorá vyžaduje, aby všetky súvisiace triedy mali spoločnú predponu, ale bolo by jasnejšie zobraziť vzťah v kóde. Ďalej musia vývojári a správcovia týchto tried spravovať tri súbory: for SlateModel, pre SlateEvent, a pre SlateListener, na implementáciu jednej koncepcie. To isté platí pre správu týchto dvoch súborov pre server Bridlica a SlateUI.

Môžeme veci vylepšiť tým, že urobíme SlateModelListener a SlateModelEvent vnorené typy SlateModel rozhranie. Pretože tieto vnorené typy sú vo vnútri rozhrania, sú implicitne statické. Na pomoc programátorovi údržby sme však použili explicitné statické vyhlásenie.

Kód klienta ich bude označovať ako SlateModel.SlateModelListener a SlateModel.SlateModelEvent, ale je to nadbytočné a zbytočne dlhé. Predponu odstránime SlateModel z vnorených tried. Po tejto zmene ich bude klientsky kód označovať ako SlateModel.Listener a SlateModel.Event. Je to krátke a jasné a nezávisí to od štandardov kódovania.

Pre SlateUI, robíme to isté - robíme z neho vnorenú triedu Bridlica a zmeniť jeho názov na UI. Pretože je to vnorená trieda vo vnútri triedy (a nie vo vnútri rozhrania), musíme použiť explicitný statický modifikátor.

S týmito zmenami potrebujeme iba jeden súbor pre triedy spojené s modelom a jeden ďalší pre triedy spojené s pohľadom. The SlateModel kód sa teraz stáva:

// SlateModel.java import java.awt.Shape; import java.util.EventListener; import java.util.EventObject; verejné rozhranie SlateModel {// Správa poslucháčov public void addSlateModelListener (SlateModel.Listener l); public void removeSlateModelListener (SlateModel.Listener l); // Správa úložiska tvarov, pre zobrazenia je potrebné upozornenie public void addShape (Shape s); public void removeShape (Tvary s); public void removeAllShapes (); // Tvarové úložisko iba na čítanie public int getShapeCount (); public Shape getShapeAtIndex (int index); // Súvisiace vnorené triedy a rozhrania najvyššej úrovne verejné rozhranie Listener rozširuje EventListener {public void slateChanged (udalosť SlateModel.Event); } public class Event extends EventObject {public Event (SlateModel model) {super (model); }}} 

A kód pre Bridlica sa mení na:

// Slate.java import java.awt. *; import javax.swing.JComponent; import javax.swing.plaf.ComponentUI; public class Slate extends JComponent implements SlateModel.Listener {public Slate (SlateModel model) {_model = model; _model.addSlateModelListener (toto); setOpaque (true); setUI (new Slate.UI ()); } public Slate () {this (new DefaultSlateModel ()); } public SlateModel getModel () {return _model; } // Implementácia poslucháča public void slateChanged (udalosť SlateModel.Event) {repaint (); } verejné rozhranie statickej triedy rozširuje ComponentUI {public void paint (Graphics g, JComponent c) {SlateModel model = ((Slate) c) .getModel (); g.setColor (c.getForeground ()); Graphics2D g2D = (Graphics2D) g; pre (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}}} 

(Zdrojový kód predvolenej implementácie pre zmenený model, DefaultSlateModel, je v súbore po / DefaultSlateModel.java.)

V rámci SlateModel triedy, nie je potrebné používať plne kvalifikované názvy pre vnorené triedy a rozhrania. Napríklad len Poslucháč by stačilo namiesto SlateModel.Listener. Používanie plne kvalifikovaných mien však pomáha vývojárom, ktorí kopírujú podpisy metód z rozhrania a vkladajú ich do implementačných tried.

JFC a použitie vnorených tried

Knižnica JFC v určitých prípadoch používa vnorené triedy. Napríklad trieda Základné hranice v balíku javax.swing.plaf.základný definuje niekoľko vnorených tried ako napr BasicBorders.ButtonBorder. V tomto prípade trieda Základné hranice nemá ďalších členov a funguje iba ako balík. Namiesto toho by bolo použitie samostatného balíka rovnako efektívne, ak nie vhodnejšie. Toto je iné použitie, ako je uvedené v tomto článku.

Použitie prístupu tohto tipu v dizajne JFC by ovplyvnilo organizáciu typov poslucháčov a udalostí súvisiacich s typmi modelov. Napríklad, javax.swing.event.TableModelListener a javax.swing.event.TableModelEvent by boli implementované ako vnorené rozhranie a vnorená trieda vo vnútri javax.swing.table.TableModel.

Táto zmena by spolu so skrátením mien viedla k pomenovaniu rozhrania poslucháča javax.swing.table.TableModel.Listener a trieda udalosti s názvom javax.swing.table.TableModel.Event. TableModel by potom boli úplne samostatné so všetkými potrebnými triedami podpory a rozhraniami, než aby boli potrebné triedy podpory a rozhranie rozložené do troch súborov a dvoch balíkov.

Pokyny na používanie vnorených tried

Rovnako ako v prípade iných vzorov, aj pri dôslednom použití vnorených tried vzniká dizajn, ktorý je jednoduchší a ľahšie pochopiteľný ako tradičná organizácia balíkov. Nesprávne použitie však vedie k zbytočnému spájaniu, čo robí úlohu vnorených tried nejasnou.

Všimnite si, že vo vnorenom príklade vyššie používame vnorené typy iba pre typy, ktoré nemôžu obstáť bez kontextu ohraničujúceho typu. Nerobíme napríklad SlateModel vnorené rozhranie Bridlica pretože s rovnakým modelom môžu existovať aj iné typy zobrazenia.

Vzhľadom na ľubovoľné dve triedy sa podľa nasledujúcich pokynov rozhodnite, či by ste mali používať vnorené triedy. Použite vnorené triedy na usporiadanie svojich tried, iba ak je odpoveď na obe otázky nižšie áno:

  1. Je možné jasne klasifikovať jednu z tried ako primárnu a druhú ako podpornú?

  2. Je podporná trieda nezmyselná, ak je primárna trieda odstránená zo subsystému?

Záver

Vzorec používania vnorených tried pevne spája príslušné typy. Vyhýba sa znečisteniu menného priestoru použitím typu ohraničenia ako menného priestoru. Výsledkom je menej zdrojových súborov bez straty možnosti verejne zverejniť podporné typy.

Rovnako ako v prípade každého iného vzoru, aj tento vzor používajte uvážlivo. Obzvlášť sa uistite, že vnorené typy skutočne súvisia a bez kontextu priloženého typu nemajú žiadny význam. Správne použitie vzoru nezvyšuje väzbu, iba objasňuje existujúcu väzbu.

Ramnivas Laddad je spoločnosťou Sun Certified Architect of Java Technology (Java 2). Má magisterský titul z elektrotechniky so špecializáciou na komunikačné inžinierstvo. Má šesťročné skúsenosti s navrhovaním a vývojom niekoľkých softvérových projektov zahŕňajúcich grafické používateľské rozhranie, sieťové pripojenie a distribuované systémy. Posledné dva roky vyvíjal objektovo orientované softvérové ​​systémy v Jave a posledných päť rokov v C ++. Ramnivas v súčasnosti pracuje v spoločnosti Real-Time Innovations Inc. ako softvérový inžinier. V RTI v súčasnosti pracuje na návrhu a vývoji ControlShell, komponentového programovacieho rámca pre tvorbu komplexných systémov v reálnom čase.
$config[zx-auto] not found$config[zx-overlay] not found