Ohjelmointi

Java Tip 75: Käytä sisäkkäisiä luokkia parempaan järjestämiseen

Tyypillinen Java-sovelluksen alijärjestelmä koostuu joukosta yhteistyössä toimivia luokkia ja rajapintoja, joista jokaisella on oma roolinsa. Jotkut näistä luokista ja rajapinnoista ovat merkityksellisiä vain muiden luokkien tai rajapintojen yhteydessä.

Kontekstiriippuvien luokkien suunnittelu ylätason sisäkkäisiksi luokiksi (sisäkkäin luokiksi, lyhyesti), jotka ovat kontekstia palvelevan luokan suljettuja, tekee tämän riippuvuuden selkeämmäksi. Sisäkkäisten luokkien käyttö helpottaa yhteistyön tunnistamista, välttää nimitilan pilaantumista ja vähentää lähdetiedostojen määrää.

(Tämän vihjeen täydellinen lähdekoodi voidaan ladata zip-muodossa Resurssit-osiosta.)

Sisäkkäiset luokat vs. sisäiset luokat

Sisäkkäiset luokat ovat yksinkertaisesti staattisia sisäisiä luokkia. Sisäkkäisten luokkien ja sisäisten luokkien välinen ero on sama kuin luokan staattisten ja ei-staattisten jäsenten välinen ero: sisäkkäiset luokat liitetään itse sulkevaan luokkaan, kun taas sisäiset luokat liittyvät sulkevan luokan objektiin.

Tämän vuoksi sisäisen luokan objektit vaativat sulkevan luokan objektin, kun taas sisäkkäiset luokan objektit eivät. Siksi sisäkkäiset luokat käyttäytyvät aivan kuten ylätason luokat ja käyttävät oheista luokkaa pakettimaisen organisaation tarjoamiseksi. Lisäksi sisäkkäisillä luokilla on pääsy kaikkiin sulkevan luokan jäseniin.

Motivaatio

Tarkastellaan tyypillistä Java-alijärjestelmää, esimerkiksi Swing-komponenttia, käyttämällä Model-View-Controller (MVC) -mallikuviota. Tapahtumaobjektit kapseloivat muutosilmoitukset mallista. Näkymät rekisteröivät kiinnostuksen erilaisiin tapahtumiin lisäämällä kuuntelijat komponentin taustamalliin. Malli ilmoittaa katsojilleen muutoksista omassa tilassaan toimittamalla nämä tapahtumakohteet rekisteröidyille kuuntelijoilleen. Usein nämä kuuntelija- ja tapahtumatyypit ovat tyypillisiä mallityypille, ja siksi ne ovat järkeviä vain mallityypin yhteydessä. Koska jokaisen näistä kuuntelija- ja tapahtumatyypeistä on oltava julkisesti saatavilla, jokaisen on oltava omassa lähdetiedostossaan. Tässä tilanteessa, ellei käytetä jotain koodauskäytäntöä, näiden tyyppien välistä kytkentää on vaikea tunnistaa. Tietenkin voidaan käyttää erillistä pakettia kullekin ryhmälle kytkennän osoittamiseksi, mutta tämä johtaa suureen määrään paketteja.

Jos toteutamme kuuntelija- ja tapahtumatyypit malliliittymän sisäkkäisiksi tyypeiksi, teemme kytkennän ilmeiseksi. Voimme käyttää mitä tahansa haluttua pääsynmuokkainta näiden sisäkkäisten tyyppien kanssa, mukaan lukien julkinen. Lisäksi koska sisäkkäiset tyypit käyttävät liitäntäkäyttöliittymää nimitilana, muu järjestelmä viittaa niihin nimellä ., välttäen nimitilan pilaantumista paketin sisällä. Malliliittymän lähdetiedostossa on kaikki tukityypit, mikä helpottaa kehittämistä ja ylläpitoa.

Ennen: Esimerkki ilman sisäkkäisiä luokkia

Kehitämme esimerkkinä yksinkertaisen komponentin, Liuskekivi, jonka tehtävänä on piirtää muotoja. Aivan kuten Swing-komponentit, käytämme MVC-suunnittelukuviota. Malli, SlateMalli, toimii muotojen arkistona. SlateModelListeners hyväksyvät muutokset malliin. Malli ilmoittaa kuulijoilleen lähettämällä tyyppisiä tapahtumia SlateModelEvent. Tässä esimerkissä tarvitaan kolme lähdetiedostoa, yksi kutakin luokkaa varten:

// SlateModel.java tuo java.awt.Shape; julkinen käyttöliittymä SlateModel {// Kuuntelijan hallinta public void addSlateModelListener (SlateModelListener l); public void removeSlateModelListener (SlateModelListener l); // Shape-arkiston hallinta, näkymät tarvitsevat ilmoituksen public void addShape (Shape s); public void removeShape (Muoto s); public void removeAllShapes (); // Shape-arkiston vain luku -toiminnot public int getShapeCount (); public Shape getShapeAtIndex (int-indeksi); } 
// SlateModelListener.java import java.util.EventListener; julkinen käyttöliittymä SlateModelListener laajentaa EventListener {public void slateChanged (SlateModelEvent -tapahtuma); } 
// SlateModelEvent.java import java.util.EventObject; julkinen luokka SlateModelEvent laajentaa EventObject {public SlateModelEvent (SlateModel-malli) {super (malli); }} 

(Lähdekoodi DefaultSlateModel, tämän mallin oletustoteutus on tiedostossa ennen / DefaultSlateModel.java.)

Seuraavaksi kiinnitämme huomiomme Liuskekivi, näkymä tälle mallille, joka välittää maalaustehtävänsä käyttöliittymän edustajalle, SlateUI:

// Slate.java tuo javax.swing.JComponent; julkinen luokka Slate laajentaa JComponent toteuttaa SlateModelListener {private SlateModel _model; julkinen liuskekivi (SlateModel-malli) {_model = malli; _model.addSlateModelListener (tämä); setOpaque (true); setUI (uusi SlateUI ()); } public Slate () {this (uusi DefaultSlateModel ()); } public SlateModel getModel () {return _model; } // Kuuntelijan toteutus public void slateChanged (SlateModelEvent event) {repaint (); }} 

Lopuksi, SlateUI, visuaalinen GUI-komponentti:

// SlateUI.java tuo java.awt. *; tuo javax.swing.JComponent; tuo javax.swing.plaf.ComponentUI; julkinen luokka SlateUI laajentaa komponenttiaUI {public void paint (Graphics g, JComponent c) {SlateModel model = ((Slate) c) .getModel (); g.setColor (c.getForeground ()); Grafiikka2D g2D = (Grafiikka2D) g; for (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}} 

Jälkeen: Muokattu esimerkki sisäkkäisillä luokilla

Yllä olevan esimerkin luokan rakenne ei osoita luokkien välistä suhdetta. Tämän lieventämiseksi olemme käyttäneet nimeämiskäytäntöä, joka edellyttää, että kaikilla liittyvillä luokilla on yhteinen etuliite, mutta olisi selvempää näyttää suhde koodina. Lisäksi näiden luokkien kehittäjien ja ylläpitäjien on hallittava kolmea tiedostoa: for SlateMalli, varten SlateEventja SlateListener, yhden konseptin toteuttamiseksi. Sama pätee näiden kahden tiedoston hallintaan Liuskekivi ja SlateUI.

Voimme parantaa asioita tekemällä SlateModelListener ja SlateModelEvent sisäkkäiset tyypit SlateMalli käyttöliittymä. Koska nämä sisäkkäiset tyypit ovat käyttöliittymän sisällä, ne ovat epäsuorasti staattisia. Siitä huolimatta olemme käyttäneet nimenomaista staattista ilmoitusta auttaaksemme huolto-ohjelmoijaa.

Asiakaskoodissa viitataan niihin nimellä SlateModel.SlateModelListener ja SlateModel.SlateModelEvent, mutta tämä on tarpeeton ja tarpeettoman pitkä. Poistamme etuliitteen SlateMalli sisäkkäisistä luokista. Tämän muutoksen myötä asiakaskoodi viittaa heihin nimellä SlateModel.Listener ja SlateModel.Event. Tämä on lyhyt ja selkeä eikä riipu koodausstandardeista.

Sillä SlateUI, teemme saman asian - teemme siitä sisäkkäisen luokan Liuskekivi ja muuta sen nimeksi UI. Koska se on sisäkkäinen luokka luokan sisällä (eikä liitännän sisällä), meidän on käytettävä nimenomaista staattista muokkainta.

Näiden muutosten myötä tarvitsemme vain yhden tiedoston malliin liittyville luokille ja yhden lisää näkymään liittyville luokille. SlateMalli koodista tulee nyt:

// SlateModel.java tuo java.awt.Shape; tuo java.util.EventListener; tuo java.util.EventObject; julkinen käyttöliittymä SlateModel {// Kuuntelijan hallinta public void addSlateModelListener (SlateModel.Listener l); public void removeSlateModelListener (SlateModel.Listener l); // Shape-arkiston hallinta, näkymät tarvitsevat ilmoituksen public void addShape (Shape s); public void removeShape (Muoto s); public void removeAllShapes (); // Shape-arkiston vain luku -toiminnot public int getShapeCount (); public Shape getShapeAtIndex (int-indeksi); // Liittyvät ylätason sisäkkäiset luokat ja käyttöliittymät julkinen käyttöliittymä Kuuntelija laajentaa EventListener {public void slateChanged (SlateModel.Event tapahtuma); } public class Event laajentaa EventObject {public Event (SlateModel-malli) {super (malli); }}} 

Ja koodi Liuskekivi muutetaan seuraavaksi:

// Slate.java tuo java.awt. *; tuo javax.swing.JComponent; tuo javax.swing.plaf.ComponentUI; julkinen luokka Slate laajentaa JComponent toteuttaa SlateModel.Listener {public Slate (SlateModel-malli) {_model = malli; _model.addSlateModelListener (tämä); setOpaque (true); setUI (uusi liuskekivi.UI ()); } public Slate () {this (uusi DefaultSlateModel ()); } public SlateModel getModel () {return _model; } // Kuuntelijan toteutus public void slateChanged (SlateModel.Event event) {repaint (); } public staattinen luokka UI laajentaa ComponentUI {public void paint (grafiikka g, JComponent c) {SlateModel model = ((Slate) c) .getModel (); g.setColor (c.getForeground ()); Grafiikka2D g2D = (Grafiikka2D) g; for (int size = model.getShapeCount (), i = 0; i <size; i ++) {g2D.draw (model.getShapeAtIndex (i)); }}}} 

(Muutetun mallin oletustoteutuksen lähdekoodi, DefaultSlateModel, on tiedostossa / DefaultSlateModel.java.)

Sisällä SlateMalli luokassa, ei ole tarpeen käyttää täysin päteviä nimiä sisäkkäisiin luokkiin ja rajapintoihin. Esimerkiksi vain Kuuntelija riittäisi SlateModel.Listener. Täysin hyväksyttyjen nimien käyttö auttaa kuitenkin kehittäjiä, jotka kopioivat menetelmän allekirjoituksia käyttöliittymästä ja liittävät ne toteutusluokkiin.

JFC ja sisäkkäisten luokkien käyttö

JFC-kirjasto käyttää sisäkkäisiä luokkia tietyissä tapauksissa. Esimerkiksi luokka BasicBorders pakkauksessa javax.swing.plaf.basic määrittää useita sisäkkäisiä luokkia, kuten BasicBorders.ButtonBorder. Tässä tapauksessa luokka BasicBorders ei ole muita jäseniä, ja se toimii vain pakettina. Erillisen paketin käyttäminen sen sijaan olisi ollut yhtä tehokasta, ellei sopivampaa. Tämä on erilainen käyttö kuin tässä artikkelissa esitetty.

Tämän vinkin käyttäminen JFC-suunnittelussa vaikuttaisi kuuntelijoiden ja mallityyppeihin liittyvien tapahtumien tyyppiin. Esimerkiksi, javax.swing.event.TableModelListener ja javax.swing.event.TableModelEvent toteutettaisiin sisäkkäisinä käyttöliittyminä ja sisäkkäisinä luokkina javax.swing.table.TableModel.

Tämä muutos yhdessä nimien lyhentämisen kanssa johtaisi nimettyyn kuuntelijarajapintaan javax.swing.table.TableModel.Listener ja nimetty tapahtumaluokka javax.swing.table.TableModel.Event. Pöytämalli olisi sitten täysin itsenäinen kaikkien tarvittavien tukiluokkien ja rajapintojen kanssa sen sijaan, että tarvitsisi tukiluokkia ja käyttöliittymää hajautettuna kolmeen tiedostoon ja kahteen pakettiin.

Ohjeet sisäkkäisten luokkien käyttämiseen

Kuten minkä tahansa muun mallin kohdalla, sisäkkäisten luokkien järkevä käyttö johtaa suunnitteluun, joka on yksinkertaisempi ja helpommin ymmärrettävissä kuin perinteinen pakettiorganisaatio. Väärä käyttö johtaa kuitenkin tarpeettomaan kytkentään, mikä tekee sisäkkäisten luokkien roolista epäselvän.

Huomaa, että yllä olevassa sisäkkäisessä esimerkissä käytämme sisäkkäisiä tyyppejä vain tyyppeihin, jotka eivät kestä ilman liitetyn tyypin kontekstia. Emme esimerkiksi tee SlateMalli sisäkkäinen käyttöliittymä Liuskekivi koska samaa mallia käyttäviä näkymiä voi olla muita.

Ottaen huomioon kaikki kaksi luokkaa, noudata seuraavia ohjeita päättääksesi, pitäisikö sinun käyttää sisäkkäisiä luokkia. Käytä sisäkkäisiä luokkia luokkien järjestämiseen vain, jos vastaus molempiin alla oleviin kysymyksiin on kyllä:

  1. Onko mahdollista luokitella yksi luokista selvästi ensisijaiseksi luokaksi ja toinen tukiluokiksi?

  2. Onko tukiluokka merkityksetön, jos ensisijainen luokka poistetaan osajärjestelmästä?

Johtopäätös

Sisäkkäisten luokkien käyttötapa yhdistää läheiset tyypit tiukasti. Se välttää nimitilan pilaantumisen käyttämällä liitteenä olevaa tyyppiä nimitilana. Se johtaa vähemmän lähdetiedostoihin menettämättä mahdollisuutta paljastaa tukityyppejä julkisesti.

Kuten mitä tahansa muuta mallia, käytä tätä mallia harkitusti. Varmista erityisesti, että sisäkkäiset tyypit ovat todella sukulaisia ​​eikä niillä ole merkitystä ilman liitettävän tyypin kontekstia. Kuvion oikea käyttö ei lisää kytkentää, vaan vain selventää olemassa olevaa kytkentää.

Ramnivas Laddad on Sun-sertifioitu Java-tekniikan (Java 2) arkkitehti. Hänellä on sähkötekniikan maisterin tutkinto erikoistumalla viestintätekniikkaan. Hänellä on kuuden vuoden kokemus useiden ohjelmistoprojektien suunnittelusta ja kehittämisestä, mukaan lukien käyttöliittymä, verkostoituminen ja hajautetut järjestelmät. Hän on kehittänyt Java-objektisuuntautuneita ohjelmistojärjestelmiä viimeiset kaksi vuotta ja C ++ -sovellusta viimeiset viisi vuotta. Ramnivas työskentelee tällä hetkellä Real-Time Innovations Inc.:ssä ohjelmistoinsinöörinä. RTI: llä hän työskentelee parhaillaan suunnittelemaan ja kehittämään ControlShellia, komponenttipohjaista ohjelmointikehystä monimutkaisten reaaliaikaisten järjestelmien rakentamiseksi.
$config[zx-auto] not found$config[zx-overlay] not found