Programiranje

Nasvet Java 75: Za boljšo organizacijo uporabite ugnezdene razrede

Tipični podsistem v aplikaciji Java je sestavljen iz nabora sodelujočih razredov in vmesnikov, ki imajo določeno vlogo. Nekateri od teh razredov in vmesnikov so smiselni samo v kontekstu drugih razredov ali vmesnikov.

Načrtovanje razredov, odvisnih od konteksta, kot ugnezdenih razredov najvišje ravni (na kratko ugnezdeni razredi), ki jih zapre razred, ki služi kontekstu, naredi to odvisnost jasnejšo. Poleg tega uporaba ugnezdenih razredov olajša prepoznavanje sodelovanja, izogne ​​se onesnaženju imenskega prostora in zmanjša število izvornih datotek.

(Celotno izvorno kodo tega nasveta lahko v obliki zip prenesete iz razdelka Viri.)

Vgnezdeni razredi v primerjavi z notranjimi razredi

Vgnezdeni razredi so preprosto statični notranji razredi. Razlika med ugnezdenimi razredi in notranjimi razredi je enaka razliki med statičnimi in nestatičnimi člani razreda: ugnezdeni razredi so povezani s samim zaprtim razredom, medtem ko so notranji razredi povezani s predmetom zaprtega razreda.

Zaradi tega predmeti notranjega razreda zahtevajo objekt zaprtega razreda, medtem ko ugnezdeni predmeti razreda ne. Vgnezdeni razredi se zato obnašajo tako kot razredi najvišje ravni in uporabljajo zaprti razred, da zagotovijo paketno organizacijo. Poleg tega imajo ugnezdeni razredi dostop do vseh članov zaprtega razreda.

Motivacija

Razmislite o tipičnem podsistemu Java, na primer komponenti Swing, z uporabo vzorčnega vzorca Model-View-Controller (MVC). Predmeti dogodkov vsebujejo obvestila o spremembah iz modela. Pogledi registrirajo zanimanje za različne dogodke z dodajanjem poslušalcev osnovnemu modelu komponente. Model svoje gledalce obvesti o spremembah v svojem stanju tako, da te predmete dogodkov dostavi registriranim poslušalcem. Pogosto so ti tipi poslušalcev in dogodkov značilni za tip modela in so zato smiselni le v kontekstu tipa modela. Ker mora biti vsak od teh vrst poslušalcev in dogodkov javno dostopen, mora biti vsak v svoji izvorni datoteki. V tem primeru je povezave med temi vrstami težko prepoznati, razen če se uporabi kakšen dogovor o kodiranju. Seveda lahko uporabimo ločen paket za vsako skupino, da prikažemo sklopko, vendar to povzroči veliko število paketov.

Če vrste poslušalcev in dogodke implementiramo kot ugnezdene vrste vmesnika modela, povezovanje postane očitno. Za te ugnezdene vrste lahko uporabimo kateri koli modifikator dostopa, vključno z javnim. Poleg tega, ker ugnezdeni tipi uporabljajo zaprti vmesnik kot imenski prostor, se preostali del sistema nanaša nanje ., izogibanje onesnaženju imenskega prostora znotraj tega paketa. Izvorna datoteka za vmesnik modela ima vse podporne vrste, kar olajša razvoj in vzdrževanje.

Pred: Primer brez ugnezdenih razredov

Kot primer razvijemo preprosto komponento, Skrilavec, katere naloga je risanje oblik. Tako kot Swing komponente uporabljamo oblikovalski vzorec MVC. Model, SlateModel, služi kot odlagališče oblik. SlateModelListeners naročite se na spremembe v modelu. Model svoje poslušalce obvesti s pošiljanjem dogodkov vrste SlateModelEvent. V tem primeru potrebujemo tri izvorne datoteke, po eno za vsak razred:

// SlateModel.java import java.awt.Shape; javni vmesnik SlateModel {// upravljanje poslušalcev javna praznina addSlateModelListener (SlateModelListener l); javna praznina removeSlateModelListener (SlateModelListener l); // upravljanje skladišča oblik, pogledi potrebujejo obveščanje public void addShape (Shape s); javna praznina removeShape (Shape s); javna praznina removeAllShapes (); // Operacije samo za branje repozitorija oblik public int getShapeCount (); javni Shape getShapeAtIndex (indeks int); } 
// SlateModelListener.java import java.util.EventListener; javni vmesnik SlateModelListener razširja EventListener {public void slateChanged (dogodek SlateModelEvent); } 
// SlateModelEvent.java import java.util.EventObject; javni razred SlateModelEvent razširja EventObject {javni SlateModelEvent (model SlateModel) {super (model); }} 

(Izvorna koda za DefaultSlateModel, privzeta izvedba za ta model je v datoteki before / DefaultSlateModel.java.)

Nato se osredotočimo na Skrilavec, pogled za ta model, ki svojo nalogo poslikave posreduje pooblaščencu uporabniškega vmesnika, SlateUI:

// Slate.java uvoz javax.swing.JComponent; javni razred Slate razširja JComponent implementira SlateModelListener {private SlateModel _model; javni Slate (model SlateModel) {_model = model; _model.addSlateModelListener (to); setOpaque (true); setUI (nov SlateUI ()); } public Slate () {this (novo DefaultSlateModel ()); } javni SlateModel getModel () {return _model; } // Izvedba poslušalca public void slateChanged (dogodek SlateModelEvent) {repaint (); }} 

Končno, SlateUI, komponenta vizualnega GUI:

// SlateUI.java import java.awt. *; uvoz javax.swing.JComponent; uvoz javax.swing.plaf.ComponentUI; javni razred SlateUI razširja ComponentUI {javna void paint (Grafika g, JComponent c) {SlateModel model = ((Slate) c) .getModel (); g.setColor (c.getForeground ()); Graphics2D g2D = (Graphics2D) g; za (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}} 

Po: Spremenjeni primer z uporabo ugnezdenih razredov

Struktura razredov v zgornjem primeru ne prikazuje razmerja med razredi. Da bi to ublažili, smo uporabili dogovor o poimenovanju, ki zahteva, da imajo vsi povezani razredi skupno predpono, vendar bi bilo jasneje prikazati razmerje v kodi. Poleg tega morajo razvijalci in vzdrževalci teh razredov upravljati tri datoteke: for SlateModel, za SlateEvent, in za SlateListener, za izvedbo enega koncepta. Enako velja za upravljanje dveh datotek za Skrilavec in SlateUI.

Stvari lahko izboljšamo z izdelavo SlateModelListener in SlateModelEvent ugnezdene vrste SlateModel vmesnik. Ker so ti ugnezdeni tipi znotraj vmesnika, so implicitno statični. Kljub temu smo uporabili izrecno statično izjavo za pomoč programerju vzdrževanja.

Koda stranke se bo nanje nanašala kot SlateModel.SlateModelListener in SlateModel.SlateModelEvent, vendar je to odveč in po nepotrebnem dolgo. Predpono odstranimo SlateModel iz ugnezdenih razredov. S to spremembo jih bo odjemalska koda označevala kot SlateModel.Listener in SlateModel.Event. To je kratko in jasno ter ni odvisno od kodnih standardov.

Za SlateUI, počnemo isto stvar - naredimo ga ugnezdeni razred Skrilavec in spremenite ime v Uporabniški vmesnik. Ker gre za ugnezdeni razred znotraj razreda (in ne znotraj vmesnika), moramo uporabiti eksplicitni statični modifikator.

S temi spremembami potrebujemo samo eno datoteko za razrede, povezane z modelom, in še eno za razrede, povezane s pogledi. The SlateModel koda zdaj postane:

// SlateModel.java uvoz java.awt.Shape; uvoz java.util.EventListener; uvoz java.util.EventObject; javni vmesnik SlateModel {// upravljanje poslušalcev javna praznina addSlateModelListener (SlateModel.Listener l); javna praznina removeSlateModelListener (SlateModel.Listener l); // upravljanje skladišča oblik, pogledi potrebujejo obveščanje public void addShape (Shape s); javna praznina removeShape (Shape s); javna praznina removeAllShapes (); // Operacije samo za branje repozitorija oblik public int getShapeCount (); javni Shape getShapeAtIndex (indeks int); // Sorodni najvišji nivoji ugnezdenih razredov in vmesnikov javni vmesnik Prisluškovalec razširja EventListener {public void slateChanged (dogodek SlateModel.Event); } dogodek javnega razreda razširja EventObject {javni dogodek (model SlateModel) {super (model); }}} 

In koda za Skrilavec se spremeni v:

// Slate.java uvoz java.awt. *; uvoz javax.swing.JComponent; uvoz javax.swing.plaf.ComponentUI; javni razred Slate razširja JComponent implementira SlateModel.Listener {javni Slate (model SlateModel) {_model = model; _model.addSlateModelListener (to); setOpaque (true); setUI (nov Slate.UI ()); } public Slate () {this (novo DefaultSlateModel ()); } javni SlateModel getModel () {return _model; } // Izvedba poslušalca public void slateChanged (dogodek SlateModel.Event) {repaint (); } javni statični uporabniški vmesnik razširi ComponentUI {javna void paint (Grafika g, JComponent c) {SlateModel model = ((Slate) c) .getModel (); g.setColor (c.getForeground ()); Graphics2D g2D = (Graphics2D) g; za (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}}} 

(Izvorna koda za privzeto izvedbo za spremenjeni model, DefaultSlateModel, je v datoteki po / DefaultSlateModel.java.)

Znotraj SlateModel razreda, ni treba uporabljati popolnoma kvalificiranih imen za ugnezdene razrede in vmesnike. Na primer, samo Poslušalec bi zadostoval namesto SlateModel.Listener. Vendar uporaba popolnoma kvalificiranih imen pomaga razvijalcem, ki kopirajo podpise metod iz vmesnika in jih prilepijo v izvedbene razrede.

JFC in uporaba ugnezdenih razredov

Knjižnica JFC v nekaterih primerih uporablja ugnezdene razrede. Na primer razred BasicBorders v paketu javax.swing.plaf.basic definira več ugnezdenih razredov, kot je BasicBorders.ButtonBorder. V tem primeru razred BasicBorders nima drugih članov in preprosto deluje kot paket. Namesto tega bi bila uporaba ločenega paketa enako učinkovita, če ne celo bolj primerna. To se razlikuje od tiste, predstavljene v tem članku.

Uporaba pristopa tega nasveta pri oblikovanju JFC bi vplivala na organizacijo vrst poslušalcev in dogodkov, povezanih s tipi modelov. Na primer, javax.swing.event.TableModelListener in javax.swing.event.TableModelEvent bi bil izveden kot ugnezdeni vmesnik in ugnezdeni razred znotraj javax.swing.table.TableModel.

Ta sprememba bi skupaj s krajšanjem imen povzročila poimenovanje vmesnika poslušalca javax.swing.table.TableModel.Listener in razred dogodkov z imenom javax.swing.table.TableModel.Event. TabelaModel bi bil potem popolnoma samozadosten z vsemi potrebnimi razredi podpore in vmesniki, namesto da bi potreboval razrede podpore in vmesnik, razporejen v tri datoteke in dva paketa.

Smernice za uporabo ugnezdenih razredov

Kot pri vseh drugih vzorcih tudi pri razumni uporabi ugnezdenih razredov pri oblikovanju, ki je preprostejše in lažje razumljivo kot pri tradicionalni organizaciji paketov. Vendar nepravilna uporaba vodi do nepotrebnega povezovanja, zaradi česar je vloga ugnezdenih razredov nejasna.

Upoštevajte, da v zgornjem ugnezdenem primeru uporabljamo ugnezdene tipe samo za tipe, ki ne morejo stati brez konteksta zaprtega tipa. Mi na primer ne delamo SlateModel ugnezdeni vmesnik Skrilavec ker lahko obstajajo tudi druge vrste pogledov, ki uporabljajo isti model.

Glede na katera koli dva razreda uporabite naslednje smernice, da se odločite, ali želite uporabljati ugnezdene razrede. Za organiziranje predavanj uporabite ugnezdene razrede le, če je odgovor na obe spodnji vprašanji pritrdilen:

  1. Ali je mogoče enega od razredov jasno razvrstiti kot osnovni razred, drugega pa kot podporni razred?

  2. Ali je podporni razred brez pomena, če je primarni razred odstranjen iz podsistema?

Zaključek

Vzorec uporabe ugnezdenih razredov tesno poveže sorodne vrste. Izogiba se onesnaženju imenskega prostora z uporabo zaprtega tipa kot imenskega prostora. Rezultat je manj izvornih datotek, ne da bi pri tem izgubili možnost javnega izpostavljanja podpornih vrst.

Kot pri vseh drugih vzorcih, uporabite ta vzorec preudarno. Zlasti zagotovite, da so ugnezdeni tipi resnično povezani in nimajo pomena brez konteksta zaprtega tipa. Pravilna uporaba vzorca ne poveča spenjanja, temveč zgolj razjasni obstoječe spenjanje.

Ramnivas Laddad je arhitekt Java tehnologije, pooblaščen za zaščito pred soncem (Java 2). Je magister elektrotehnike s specializacijo iz komunikacijske tehnike. Ima šest let izkušenj z oblikovanjem in razvojem več projektov programske opreme, ki vključujejo GUI, mreženje in distribuirane sisteme. Objektivno usmerjene programske sisteme je razvil v Javi v zadnjih dveh letih in v C ++ v zadnjih petih letih. Ramnivas trenutno dela v podjetju Real-Time Innovations Inc. kot programski inženir. Trenutno si pri RTI prizadeva za oblikovanje in razvoj ControlShell, komponentnega programskega okvira za gradnjo kompleksnih sistemov v realnem času.
$config[zx-auto] not found$config[zx-overlay] not found