Programování

Tip Java 75: Použijte vnořené třídy pro lepší organizaci

Typický subsystém v aplikaci Java se skládá ze sady spolupracujících tříd a rozhraní, z nichž každé plní určitou roli. Některé z těchto tříd a rozhraní mají smysl pouze v kontextu jiných tříd nebo rozhraní.

Navrhování kontextově závislých tříd jako vnořených tříd nejvyšší úrovně (zkráceně vnořených tříd) uzavřených třídou poskytující kontext činí tuto závislost jasnější. Kromě toho použití vnořených tříd usnadňuje rozpoznávání spolupráce, předchází znečištění oboru názvů a snižuje počet zdrojových souborů.

(Celý zdrojový kód pro tento tip lze stáhnout ve formátu zip v části Zdroje.)

Vnořené třídy vs. vnitřní třídy

Vnořené třídy jsou jednoduše statické vnitřní třídy. Rozdíl mezi vnořenými třídami a vnitřními třídami je stejný jako rozdíl mezi statickými a nestatickými členy třídy: vnořené třídy jsou přidruženy k samotné uzavírající třídě, zatímco vnitřní třídy jsou spojeny s objektem uzavírající třídy.

Z tohoto důvodu vyžadují objekty vnitřní třídy objekt obklopující třídy, zatímco objekty vnořené třídy nikoli. Vnořené třídy se proto chovají stejně jako třídy nejvyšší úrovně, přičemž k zajištění organizace podobné balíčku používají uzavřenou třídu. Vnořené třídy mají navíc přístup ke všem členům obklopující třídy.

Motivace

Zvažte typický subsystém Java, například komponentu Swing, pomocí návrhového vzoru Model-View-Controller (MVC). Objekty události zapouzdřují oznámení o změnách z modelu. Pohledy registrují zájem o různé události přidáním posluchačů do základního modelu komponenty. Model upozorňuje své diváky na změny ve svém vlastním stavu doručováním těchto objektů událostí svým registrovaným posluchačům. Tyto typy posluchačů a událostí jsou často specifické pro typ modelu, a proto dávají smysl pouze v kontextu typu modelu. Protože každý z těchto typů posluchače a typu události musí být veřejně přístupný, musí být každý ve svém vlastním zdrojovém souboru. V této situaci, pokud není použita nějaká konvence kódování, je obtížné rozpoznat spojení mezi těmito typy. Samozřejmě lze pro každou skupinu použít samostatný balíček pro zobrazení spojení, ale výsledkem je velký počet balíčků.

Pokud implementujeme typy posluchačů a událostí jako vnořené typy rozhraní modelu, uděláme spojku zřejmou. U těchto vnořených typů můžeme použít libovolný požadovaný modifikátor přístupu, včetně public. Kromě toho, protože vnořené typy používají uzavírací rozhraní jako jmenný prostor, zbytek systému je označuje jako ., vyhnout se znečištění oboru názvů uvnitř tohoto balíčku. Zdrojový soubor pro modelové rozhraní má všechny podpůrné typy, což usnadňuje vývoj a údržbu.

Před: Příklad bez vnořených tříd

Jako příklad vyvíjíme jednoduchou součást, Břidlice, jehož úkolem je kreslit tvary. Stejně jako komponenty Swing používáme návrhový vzor MVC. Model, SlateModel, slouží jako úložiště tvarů. SlateModelListenerPřihlaste se k odběru změn v modelu. Model upozorní své posluchače odesláním událostí typu SlateModelEvent. V tomto příkladu potřebujeme tři zdrojové soubory, jeden pro každou třídu:

// SlateModel.java import java.awt.Shape; veřejné rozhraní SlateModel {// Správa posluchače public void addSlateModelListener (SlateModelListener l); public void removeSlateModelListener (SlateModelListener l); // Správa úložiště tvarů, pohledy vyžadují oznámení public void addShape (Shape s); public void removeShape (Shape s); public void removeAllShapes (); // Shape repository read-only operations public int getShapeCount (); public Shape getShapeAtIndex (int index); } 
// SlateModelListener.java import java.util.EventListener; veřejné rozhraní SlateModelListener rozšiřuje EventListener {public void slateChanged (událost SlateModelEvent); } 
// SlateModelEvent.java import java.util.EventObject; veřejná třída SlateModelEvent rozšiřuje EventObject {public SlateModelEvent (model SlateModel) {super (model); }} 

(Zdrojový kód pro DefaultSlateModel, výchozí implementace pro tento model, je v souboru before / DefaultSlateModel.java.)

Dále obrátíme naši pozornost k Břidlice, pohled pro tento model, který předává svůj obrazový úkol delegátovi uživatelského rozhraní, SlateUI:

// Slate.java import javax.swing.JComponent; public class Slate extends JComponent implements SlateModelListener {private SlateModel _model; public Slate (model SlateModel) {_model = model; _model.addSlateModelListener (toto); setOpaque (true); setUI (nový SlateUI ()); } public Slate () {this (new DefaultSlateModel ()); } public SlateModel getModel () {return _model; } // Implementace posluchače public void slateChanged (událost SlateModelEvent) {repaint (); }} 

Konečně, SlateUI, vizuální GUI komponenta:

// 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; for (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}} 

Po: Upravený příklad využívající vnořené třídy

Struktura třídy ve výše uvedeném příkladu neukazuje vztah mezi třídami. Abychom to zmírnili, použili jsme konvenci pojmenování, která vyžaduje, aby všechny související třídy měly společnou předponu, ale bylo by jasnější ukázat vztah v kódu. Kromě toho musí vývojáři a správci těchto tříd spravovat tři soubory: for SlateModel, pro SlateEvent, a pro SlateListener, implementovat jeden koncept. Totéž platí pro správu těchto dvou souborů pro Břidlice a SlateUI.

Můžeme věci vylepšit tím, že uděláme SlateModelListener a SlateModelEvent vnořené typy SlateModel rozhraní. Protože tyto vnořené typy jsou uvnitř rozhraní, jsou implicitně statické. Nicméně jsme použili explicitní statické prohlášení, abychom pomohli programátorovi údržby.

Klientský kód je bude označovat jako SlateModel.SlateModelListener a SlateModel.SlateModelEvent, ale je to nadbytečné a zbytečně dlouhé. Odstraníme předponu SlateModel ze vnořených tříd. S touto změnou bude klientský kód na ně odkazovat jako SlateModel.Listener a SlateModel.Event. To je krátké a jasné a nezávisí to na kódovacích standardech.

Pro SlateUIděláme totéž - děláme z něj vnořenou třídu Břidlice a změňte jeho název na UI. Protože se jedná o vnořenou třídu uvnitř třídy (a ne uvnitř rozhraní), musíme použít explicitní statický modifikátor.

S těmito změnami potřebujeme pouze jeden soubor pro třídy související s modelem a jeden další pro třídy související s pohledem. The SlateModel kód se nyní stává:

// SlateModel.java import java.awt.Shape; import java.util.EventListener; import java.util.EventObject; veřejné rozhraní SlateModel {// Správa posluchače public void addSlateModelListener (SlateModel.Listener l); public void removeSlateModelListener (SlateModel.Listener l); // Správa úložiště tvarů, pohledy vyžadují oznámení public void addShape (Shape s); public void removeShape (Shape s); public void removeAllShapes (); // Shape repository read-only operations public int getShapeCount (); public Shape getShapeAtIndex (int index); // Související vnořené třídy a rozhraní nejvyšší úrovně veřejné rozhraní Listener rozšiřuje EventListener {public void slateChanged (událost SlateModel.Event); } public class Event extends EventObject {public Event (SlateModel model) {super (model); }}} 

A kód pro Břidlice se změní 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; } // Implementace posluchače public void slateChanged (událost SlateModel.Event) {repaint (); } veřejné statické uživatelské rozhraní rozšiřuje ComponentUI {public void paint (Graphics g, JComponent c) {SlateModel model = ((Slate) c) .getModel (); g.setColor (c.getForeground ()); Graphics2D g2D = (Graphics2D) g; for (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}}} 

(Zdrojový kód pro výchozí implementaci pro změněný model, DefaultSlateModel, je v souboru po / DefaultSlateModel.java.)

V rámci SlateModel třídy, není nutné používat plně kvalifikované názvy pro vnořené třídy a rozhraní. Například jen Posluchač by stačilo místo SlateModel.Listener. Použití plně kvalifikovaných názvů však pomáhá vývojářům, kteří kopírují podpisy metod z rozhraní a vkládají je do implementačních tříd.

JFC a použití vnořených tříd

Knihovna JFC v určitých případech používá vnořené třídy. Například třída Základní hranice v balení javax.swing.plaf.základní definuje několik vnořených tříd, jako je BasicBorders.ButtonBorder. V tomto případě třída Základní hranice nemá žádné další členy a jednoduše funguje jako balíček. Místo toho by použití samostatného balíčku bylo stejně účinné, ne-li vhodnější. Toto je jiné použití, než jaké je uvedeno v tomto článku.

Použití přístupu tohoto tipu v designu JFC by ovlivnilo organizaci typů posluchačů a událostí souvisejících s typy modelů. Například, javax.swing.event.TableModelListener a javax.swing.event.TableModelEvent bude implementováno respektive jako vnořené rozhraní a vnořená třída uvnitř javax.swing.table.TableModel.

Tato změna by spolu se zkrácením názvů vedla k pojmenování rozhraní posluchače javax.swing.table.TableModel.Listener a třída události s názvem javax.swing.table.TableModel.Event. TableModel by pak byl plně soběstačný se všemi nezbytnými třídami podpory a rozhraními, spíše než potřebovat třídy podpory a rozhraní rozložené na tři soubory a dva balíčky.

Pokyny pro používání vnořených tříd

Stejně jako u jiných vzorů vede uvážlivé použití vnořených tříd k designu, který je jednodušší a snáze srozumitelný než tradiční organizace balíčků. Nesprávné použití však vede k zbytečnému propojení, díky čemuž je role vnořených tříd nejasná.

Všimněte si, že ve vnořeném příkladu výše používáme vnořené typy pouze pro typy, které nemohou obstát bez kontextu uzavírajícího typu. My například nevyrábíme SlateModel vnořené rozhraní Břidlice protože se stejným modelem mohou existovat i jiné typy zobrazení.

Vzhledem k tomu, že máte dvě třídy, použijte následující pokyny k rozhodnutí, zda byste měli používat vnořené třídy. Pomocí vnořených tříd můžete své kurzy organizovat pouze v případě, že odpověď na obě níže uvedené otázky je ano:

  1. Je možné jasně klasifikovat jednu ze tříd jako primární a druhou jako podpůrnou?

  2. Je podpůrná třída bezvýznamná, pokud je primární třída odstraněna ze subsystému?

Závěr

Vzor použití vnořených tříd pevně spojuje související typy. Zabraňuje znečištění oboru názvů pomocí typu uzavření jako oboru názvů. Výsledkem je méně zdrojových souborů, aniž by došlo ke ztrátě možnosti veřejně vystavit podpůrné typy.

Stejně jako u jiných vzorů používejte tento vzor uvážlivě. Zejména zajistěte, aby vnořené typy byly skutečně související a neměly žádný význam bez kontextu uzavírajícího typu. Správné použití vzoru nezvyšuje vazbu, ale pouze objasňuje existující vazbu.

Ramnivas Laddad je Sun Certified Architect of Java Technology (Java 2). Vystudoval elektrotechniku ​​se specializací na komunikační inženýrství. Má šest let zkušeností s navrhováním a vývojem několika softwarových projektů zahrnujících grafické uživatelské rozhraní, sítě a distribuované systémy. Poslední dva roky vyvíjel objektově orientované softwarové systémy v Javě a posledních pět let v C ++. Ramnivas v současné době pracuje ve společnosti Real-Time Innovations Inc. jako softwarový inženýr. V RTI v současné době pracuje na návrhu a vývoji ControlShell, programovacího rámce založeného na komponentách pro budování komplexních systémů v reálném čase.
$config[zx-auto] not found$config[zx-overlay] not found