Ohjelmointi

Johdatus muotoilumalleihin, osa 2: Uudelleen tarkasteltu neljän klassikon jengi

Tämän kolmiosaisen suunnittelumalleja esittävän sarjan osassa 1 viittasin Suunnittelumallit: Uudelleenkäytettävän olio-suunnittelun elementit. Tämän klassikon ovat kirjoittaneet Erich Gamma, Richard Helm, Ralph Johnson ja John Vlissides, jotka tunnetaan yhdessä nimellä Neljän jengi. Kuten useimmat lukijat tietävät, Suunnittelumalleja esittelee 23 ohjelmistosuunnittelumallia, jotka sopivat osiin 1: Luova, rakenteellinen ja käyttäytyminen.

Suunnittelumallit JavaWorldilla

David Gearyn Java-suunnittelumallien sarja on mestarillinen esittely moniin Java-koodin neljästä joukosta -malleista.

Suunnittelumalleja on kanoninen lukeminen ohjelmistokehittäjille, mutta monet uudet ohjelmoijat haastavat sen viitemuoto ja laajuus. Jokainen 23 kuviosta on kuvattu yksityiskohtaisesti 13 osasta koostuvassa mallimuodossa, joka voi olla paljon sulavaa. Toinen haaste uusille Java-kehittäjille on, että neljän ryhmän joukko lähtee olio-ohjelmoinnista, ja esimerkit perustuvat C ++ - ja Smalltalk-koodeihin, ei Java-koodiin.

Pakkaan tässä opetusohjelmassa kaksi yleisimmin käytettyä mallia - Strategia ja Vierailija - Java-kehittäjien näkökulmasta. Strategia on melko yksinkertainen kuvio, joka toimii esimerkkinä siitä, miten jalkasi kastellaan GoF-suunnittelumalleilla yleensä; Vierailija on monimutkaisempi ja keskisuuri. Aloitan esimerkistä, jonka pitäisi demystifioida kaksoislähetysmekanismi, joka on tärkeä osa vierailijakuviota. Sitten esittelen Visitor-mallin kääntäjän käyttötapauksessa.

Seuraamalla esimerkkejäni pitäisi auttaa sinua tutkimaan ja käyttämään muita GoF-malleja itsellesi. Lisäksi tarjoan vinkkejä, jotta saat kaiken irti Neljä jengi -kirjasta, ja lopetan yhteenvedolla kritiikistä suunnittelumallien käytöstä ohjelmistokehityksessä. Tämä keskustelu voi olla erityisen merkityksellinen ohjelmoinnin uusille kehittäjille.

Purkamisen strategia

Strategia kaavan avulla voit määrittää algoritmiperheen, kuten lajittelussa, tekstin koostumuksessa tai asettelun hallinnassa käytetyt algoritmit. Strategian avulla voit myös kapseloida jokaisen algoritmin omaan luokkaansa ja tehdä niistä vaihdettavissa. Jokainen kapseloitu algoritmi tunnetaan nimellä strategia. Suorituksen aikana asiakas valitsee vaatimuksilleen sopivan algoritmin.

Mikä on asiakas?

A asiakas on mikä tahansa ohjelmisto, joka on vuorovaikutuksessa suunnittelukuvion kanssa. Vaikka asiakas on tyypillisesti objekti, asiakas voi myös olla koodi sovelluksen sisällä public staattinen void main (String [] väittää) menetelmä.

Toisin kuin Decorator-kuvio, joka keskittyy kohteen mallin muuttamiseen iho, tai ulkonäkö, Strategia keskittyy muuttamaan kohteen suolet, mikä tarkoittaa sen muuttuvaa käyttäytymistä. Strategian avulla voit välttää useiden ehdollisten lauseiden käyttöä siirtämällä ehdolliset haarat omiin strategialuokkiinsa. Nämä luokat ovat usein peräisin abstraktista superluokasta, jonka asiakas viittaa ja käyttää vuorovaikutuksessa tietyn strategian kanssa.

Abstraktista näkökulmasta strategia sisältää Strategia, BetoniStrategiaxja Asiayhteys tyypit.

Strategia

Strategia tarjoaa yhteisen käyttöliittymän kaikille tuetuille algoritmeille. Luettelossa 1 esitetään Strategia käyttöliittymä.

Listaus 1. void execute (int x) on toteutettava kaikissa konkreettisissa strategioissa

julkisen käyttöliittymän strategia {public void execute (int x); }

Jos konkreettisia strategioita ei ole parametroitu yhteisillä tiedoilla, voit toteuttaa ne Java: n kautta käyttöliittymä ominaisuus. Jos ne on parametrisoitu, ilmoitat sen sijaan abstraktin luokan. Esimerkiksi tasaus oikealle, tasaus keskelle ja perustelu tekstin tasausstrategioille jakaa käsitteen a leveys jossa suoritetaan tekstin tasaus. Joten julistaisit tämän leveys abstraktissa luokassa.

BetoniStrategiax

Jokainen BetoniStrategiax toteuttaa yhteisen käyttöliittymän ja tarjoaa algoritmin toteutuksen. Listaus 2 toteuttaa Listaus 1: n Strategia käyttöliittymä kuvaamaan tiettyä konkreettista strategiaa.

Listaus 2. ConcreteStrategyA suorittaa yhden algoritmin

julkinen luokka ConcreteStrategyA toteuttaa strategiaa {@Override public void execute (int x) {System.out.println ("toteutusstrategia A: x =" + x); }}

void execute (int x) Listing 2 -menetelmä tunnistaa tietyn strategian. Ajattele tätä menetelmää abstraktina jollekin hyödyllisemmälle, kuten tietyntyyppiselle lajittelualgoritmille (esim. Bubble Sort, Insertion Sort tai Quick Sort) tai tietyntyyppiselle asettelunhallinnalle (esim. Flow Layout, Border Layout tai Ruudukon asettelu).

Luettelossa 3 on toinen Strategia toteutus.

Listaus 3. ConcreteStrategyB suorittaa toisen algoritmin

julkinen luokka ConcreteStrategyB toteuttaa strategiaa {@Override public void execute (int x) {System.out.println ("toteutusstrategia B: x =" + x); }}

Asiayhteys

Asiayhteys tarjoaa kontekstin, jossa konkreettiseen strategiaan vedotaan. Luettelot 2 ja 3 osoittavat, että tiedot siirretään kontekstista strategiaan menetelmäparametrin kautta. Koska kaikki konkreettiset strategiat jakavat yleisen strategiarajapinnan, jotkut niistä eivät välttämättä vaadi kaikkia parametreja. Parametrien tuhlaamisen välttämiseksi (varsinkin kun välitetään monia erilaisia ​​argumentteja vain muutamalle konkreettiselle strategialle), voit antaa sen sijaan viitteen kontekstiin.

Sen sijaan, että välität kontekstiviittauksen menetelmään, voit tallentaa sen abstraktille luokalle, jolloin metodisi kutsutaan parametrittomiksi. Kontekstissa olisi kuitenkin määriteltävä laajempi käyttöliittymä, joka sisältäisi sopimuksen kontekstidatan saatavuudesta yhtenäisellä tavalla. Listauksessa 4 esitetty tulos on tiukempi kytkentä strategioiden ja niiden kontekstien välillä.

Listaus 4. Konteksti on määritetty ConcreteStrategyx-ilmentymällä

luokan konteksti {yksityinen strategiastrategia; julkinen konteksti (strategiastrategia) {setStrategy (strategia); } public void executeStrategy (int x) {strategy.execute (x); } public void setStrategy (strategiastrategia) {this.strategy = strategia; }}

Asiayhteys Luettelossa 4 oleva luokka tallentaa strategian, kun se on luotu, tarjoaa menetelmän strategian myöhemmäksi muuttamiseksi ja tarjoaa toisen menetelmän nykyisen strategian toteuttamiseksi. Lukuun ottamatta strategian välittämistä rakentajalle, tämä malli näkyy java.awt .Container-luokassa, jonka void setLayout (LayoutManager hallitsija) ja void doLayout () menetelmät määrittävät ja suorittavat asettelunhallintastrategian.

Strategiademo

Tarvitsemme asiakkaan osoittamaan edelliset tyypit. Listalla 5 on a Strategiademo asiakasluokka.

Listaus 5. StrategyDemo

public class StrategyDemo {public static void main (String [] args) {Context context = new Context (uusi ConcreteStrategyA ()); context.executeStrategy (1); context.setStrategy (uusi ConcreteStrategyB ()); context.executeStrategy (2); }}

Konkreettinen strategia liittyy a Asiayhteys esimerkiksi kun konteksti luodaan. Strategiaa voidaan myöhemmin muuttaa kontekstimenetelmäkutsun avulla.

Jos koot nämä luokat ja suoritat Strategiademo, sinun tulee huomioida seuraava tulos:

strategian toteutus A: x = 1 strategian toteutus B: x = 2

Vierailijan mallin tarkistaminen

Vierailija on viimeinen ohjelmistosuunnittelumalli, joka näkyy Suunnittelumalleja. Vaikka tämä käyttäytymismalli esitetään kirjassa viimeisenä aakkosjärjestyksestä, jotkut uskovat, että sen pitäisi tulla viimeiseksi monimutkaisuutensa vuoksi. Vierailijoiden uudet tulijat kamppailevat usein tämän ohjelmistosuunnittelumallin kanssa.

Kuten selitettiin Suunnittelumalleja, kävijä antaa sinun lisätä toimintoja luokkiin muuttamatta niitä, vähän taikaa, jota helpottaa niin sanottu kaksoislähetystekniikka. Vierailijamallin ymmärtämiseksi meidän on ensin sulatettava kaksinkertainen lähetys.

Mikä on kaksinkertainen lähetys?

Java ja monet muut kielet tukevat polymorfismi (monia muotoja) tekniikalla, joka tunnetaan nimellä dynaaminen lähetys, jossa viesti on kartoitettu tiettyyn koodisarjaan ajon aikana. Dynaaminen lähetys luokitellaan joko yksittäiseksi tai monilähetykseksi:

  • Yksi lähetys: Kun otetaan huomioon luokkahierarkia, jossa kukin luokka toteuttaa saman menetelmän (eli jokainen alaluokka ohittaa menetelmän edellisen luokan version), ja kun annetaan muuttuja, jolle on määritetty jonkin näistä luokista esiintymä, tyyppi voidaan selvittää vain osoitteessa ajonaikainen. Oletetaan esimerkiksi, että jokainen luokka toteuttaa menetelmän Tulosta(). Oletetaan myös, että yksi näistä luokista on instantoitu ajon aikana ja sen muuttuja on osoitettu muuttujalle a. Kun Java-kääntäjä kohtaa a. tulos ();, se voi vain tarkistaa sen atyyppi sisältää a Tulosta() menetelmä. Se ei tiedä, mihin tapaan soittaa. Suorituksen aikana virtuaalikone tutkii viittausta muuttujassa a ja selvittää todellisen tyypin oikean menetelmän kutsumiseksi. Tämä tilanne, jossa toteutus perustuu yhteen tyyppiin (esimerkin tyyppiin), tunnetaan nimellä yksi lähetys.
  • Useita lähetyksiä: Toisin kuin yksittäisessä lähetyksessä, jossa yksi argumentti määrittää, minkä nimisen menetelmän on käytettävä, useita lähetyksiä käyttää kaikkia argumenttejaan. Toisin sanoen se yleistää dynaamisen lähettämisen toimimaan kahden tai useamman kohteen kanssa. (Huomaa, että yksittäisen lähetyksen argumentti määritetään tyypillisesti pisteerottimella kutsutun menetelmän nimen vasemmalla puolella, kuten a sisään a.print ().)

Lopuksi, kaksinkertainen lähetys on erityistapaus monilähetyksestä, jossa kahden objektin ajonaikaiset tyypit ovat mukana puhelussa. Vaikka Java tukee yksittäistä lähetystä, se ei tue kaksoislähetystä suoraan. Mutta voimme simuloida sitä.

Luotammeko liikaa kaksinkertaiseen lähetykseen?

Bloggaaja Derek Greer uskoo, että kaksoislähetyksen käyttö voi viitata suunnitteluongelmaan, mikä voi vaikuttaa sovelluksen ylläpidettävyyteen. Lue Greerin "Double lähetys on koodihaju" -blogiviesti ja siihen liittyvät kommentit saadaksesi lisätietoja.

Simuloidaan kaksoislähetystä Java-koodissa

Wikipedian merkintä kaksoislähetyksestä tarjoaa C ++ -pohjaisen esimerkin, joka osoittaa sen olevan enemmän kuin toimintojen ylikuormitus. Luettelossa 6 esitän Java-vastaavan.

Listaus 6. Kaksoislähetys Java-koodissa

julkinen luokka DDDemo {public static void main (String [] args) {Asteroid theAsteroid = new Asteroid (); SpaceShip theSpaceShip = uusi SpaceShip (); ApolloSpacecraft theApolloSpacecraft = uusi ApolloSpacecraft (); theAsteroid.collideWith (theSpaceShip); theAsteroid.collideWith (ApolloSpacecraft); System.out.println (); ExplodingAsteroid theExplodingAsteroid = uusi ExplodingAsteroid (); theExplodingAsteroid.collideWith (theSpaceShip); theExplodingAsteroid.collideWith (theApolloSpacecraft); System.out.println (); Asteroidi theAsteroidReference = räjähtävä asteroidi; theAsteroidReference.collideWith (theSpaceShip); theAsteroidReference.collideWith (theApolloSpacecraft); System.out.println (); SpaceShip theSpaceShipReference = theApolloSpacecraft; theAsteroid.collideWith (theSpaceShipReference); theAsteroidReference.collideWith (theSpaceShipReference); System.out.println (); theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = räjähtävä asteroidi; theSpaceShipReference.collideWith (asteroidi); theSpaceShipReference.collideWith (theAsteroidReference); }} luokan avaruusalus {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} -luokan ApolloSpacecraft laajentaa SpaceShip-alusta {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} luokan asteroidi {void collideWith (SpaceShip s) {System.out.println ("Asteroidi osui avaruusalukseen"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Asteroidi osui ApolloSpacecraftiin"); }} -luokka ExplodingAsteroid laajentaa asteroidia {void collideWith (SpaceShip s) {System.out.println ("ExplodingAsteroid osui avaruusalukseen"); } void collideWith (ApolloSpacecraft as) {System.out.println ("ExplodingAsteroid osui ApolloSpacecraftiin"); }}

Listaus 6 seuraa C ++ -vastapuoliaan mahdollisimman tarkasti. Viimeiset neljä riviä main () menetelmä yhdessä void collideWith (asteroidi asteroidissa) menetelmiä Avaruusalus ja Apollo-avaruusalus osoittaa ja simuloida kaksoislähetystä.

Harkitse seuraavaa otetta vuoden lopusta main ():

theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = räjähtävä asteroidi; theSpaceShipReference.collideWith (asteroidi); theSpaceShipReference.collideWith (theAsteroidReference);

Kolmas ja neljäs rivi käyttävät yhtä lähetystä selvittääkseen oikean törmätä() menetelmä (vuonna Avaruusalus tai Apollo-avaruusalus) vedota. Tämän päätöksen tekee virtuaalikone tiedostoon tallennetun viitteen tyypin perusteella theSpaceShipReference.

Sisältä törmätä(), inAsteroid.collideWith (tämä); käyttää yhtä lähetystä selvittääkseen oikean luokan (Asteroidi tai RäjähtäväAsteroidi), joka sisältää halutun törmätä() menetelmä. Koska Asteroidi ja RäjähtäväAsteroidi ylikuormitus törmätä(), argumenttityyppi Tämä (Avaruusalus tai Apollo-avaruusalus) käytetään erottamaan oikea törmätä() tapa soittaa.

Ja sen avulla olemme saaneet aikaan kaksoislähetyksen. Yhteenvetona, soitimme ensin törmätä() sisään Avaruusalus tai Apollo-avaruusalusja käytti sitten argumenttinsa ja Tämä soittaa jollekin törmätä() menetelmiä Asteroidi tai RäjähtäväAsteroidi.

Kun juokset DDDemo, sinun tulee huomioida seuraava tulos:

$config[zx-auto] not found$config[zx-overlay] not found