Programovanie

Úvod do návrhových vzorov, časť 2: Prehodnotila sa klasika Gang-of-four

V časti 1 tejto trojdielnej série predstavujúcej dizajnové vzory som sa zmienil Dizajnové vzory: Prvky opakovane použiteľného objektovo orientovaného dizajnu. Túto klasiku napísali Erich Gamma, Richard Helm, Ralph Johnson a John Vlissides, ktorí boli súhrnne známi ako Gang štyroch. Ako väčšina čitateľov bude vedieť, Dizajnové vzory predstavuje 23 vzorov návrhu softvéru, ktoré zapadajú do kategórií diskutovaných v časti 1: Tvorivé, štrukturálne a behaviorálne.

Dizajnové vzory v prostredí JavaWorld

Séria návrhových vzorov Java od Davida Gearyho je majstrovským úvodom do mnohých vzorov Gang of Four v kóde Java.

Dizajnové vzory je kanonické čítanie pre vývojárov softvéru, ale mnohých nových programátorov napáda jeho referenčný formát a rozsah. Každý z 23 vzorov je podrobne opísaný vo formáte šablóny pozostávajúcej z 13 častí, ktoré môžu byť veľa stráviteľné. Ďalšou výzvou pre nových vývojárov Java je, že vzory Gang of Four pochádzajú z objektovo orientovaného programovania, s príkladmi založenými na C ++ a Smalltalk, nie na kóde Java.

V tomto návode rozbalím dva z bežne používaných vzorov - Strategy a Visitor - z pohľadu vývojára Java. Stratégia je pomerne jednoduchý vzor, ​​ktorý slúži ako príklad toho, ako si môžete všeobecne namočiť nohy s návrhovými vzormi GoF; Návštevník je komplexnejší a stredného rozsahu. Začnem príkladom, ktorý by mal demystifikovať mechanizmus dvojitého odoslania, ktorý je dôležitou súčasťou modelu Návštevník. Potom ukážem vzor návštevníka v prípade použitia kompilátora.

Nasledovanie mojich príkladov by vám malo pomôcť preskúmať a použiť ďalšie vzory GoF pre seba. Okrem toho ponúknem tipy, ako čo najlepšie využiť knihu Gang štyroch, a na záver zhrniem kritiku použitia návrhových vzorov pri vývoji softvéru. Táto diskusia by mohla byť obzvlášť dôležitá pre vývojárov, ktorí začínajú programovať.

Stratégia rozbalenia

The Stratégia pattern vám umožňuje definovať skupinu algoritmov, ako sú algoritmy používané na triedenie, zloženie textu alebo správu rozloženia. Stratégia tiež umožňuje zapuzdriť každý algoritmus do jeho vlastnej triedy a umožniť ich vzájomnú zameniteľnosť. Každý zapuzdrený algoritmus je známy ako a stratégia. Za behu si klient zvolí vhodný algoritmus pre svoje požiadavky.

Čo je klient?

A zákazník je akýkoľvek softvér, ktorý interaguje s návrhovým vzorom. Aj keď je to typicky objekt, klientom môže byť aj kód v rámci aplikácie public static void main (reťazec [] args) metóda.

Na rozdiel od vzoru Decorator, ktorý sa zameriava na zmenu objektu koža, alebo vzhľad, sa stratégia zameriava na zmenu objektu vnútornosti, čo znamená jeho premenlivé správanie. Stratégia vám umožní vyhnúť sa použitiu viacerých podmienených príkazov presunutím podmienených vetiev do ich vlastných tried stratégie. Tieto triedy často pochádzajú z abstraktnej nadtriedy, na ktorú klient odkazuje a používa ju pri interakcii s konkrétnou stratégiou.

Z abstraktného hľadiska zahŕňa Stratégia Stratégia, ConcreteStrategyXa Kontext typy.

Stratégia

Stratégia poskytuje spoločné rozhranie pre všetky podporované algoritmy. Zoznam 1 predstavuje Stratégia rozhranie.

Zoznam 1. void execute (int x) musí byť implementovaný vo všetkých konkrétnych stratégiách

public interface Strategy {public void execute (int x); }

Ak konkrétne stratégie nie sú parametrizované bežnými údajmi, môžete ich implementovať prostredníctvom Java rozhranie vlastnosť. Tam, kde sú parametrizované, by ste namiesto toho vyhlásili abstraktnú triedu. Napríklad stratégie zarovnania doprava, zarovnanie na stred a zarovnanie textu zdieľajú koncept a šírka v ktorom sa má vykonať zarovnanie textu. Toto by ste teda vyhlásili šírka v abstraktnej triede.

ConcreteStrategyX

Každý ConcreteStrategyX implementuje spoločné rozhranie a poskytuje implementáciu algoritmu. Zoznam 2 implementuje zoznam 1 Stratégia konkrétnu konkrétnu stratégiu.

Zoznam 2. ConcreteStrategyA vykoná jeden algoritmus

verejná trieda ConcreteStrategyA implementuje stratégiu {@Override public void execute (int x) {System.out.println ("vykonávanie stratégie A: x =" + x); }}

The void execute (int x) metóda v zozname 2 identifikuje konkrétnu stratégiu. Predstavte si túto metódu ako abstrakciu pre niečo užitočnejšie, ako je konkrétny druh algoritmu triedenia (napr. Bubble Sort, Insertion Sort alebo Quick Sort) alebo ako konkrétny druh manažéra rozloženia (napr. Flow Layout, Border Layout alebo Rozloženie mriežky).

Zoznam 3 predstavuje sekundu Stratégia implementácia.

Zoznam 3. ConcreteStrategyB vykoná iný algoritmus

verejná trieda ConcreteStrategyB implementuje stratégiu {@Override public void execute (int x) {System.out.println ("stratégia vykonávania B: x =" + x); }}

Kontext

Kontext poskytuje kontext, v ktorom sa uplatňuje konkrétna stratégia. Výpisy 2 a 3 zobrazujú údaje prenášané z kontextu do stratégie prostredníctvom parametra metódy. Pretože generické strategické rozhranie zdieľajú všetky konkrétne stratégie, niektoré z nich nemusia vyžadovať všetky parametre. Aby ste sa vyhli zbytočným parametrom (najmä keď prenášate veľa rôznych druhov argumentov iba na niekoľko konkrétnych stratégií), môžete namiesto toho odovzdať odkaz na kontext.

Namiesto toho, aby ste odovzdali kontextový odkaz na metódu, môžete ju uložiť v abstraktnej triede a vaša metóda tak bude volaná bez parametrov. V kontexte by však bolo potrebné určiť rozsiahlejšie rozhranie, ktoré by obsahovalo zmluvu o jednotnom prístupe k kontextovým údajom. Výsledkom, ktorý je uvedený v zozname 4, je užšie prepojenie medzi stratégiami a ich kontextom.

Výpis 4. Kontext je konfigurovaný pomocou inštancie ConcreteStrategyx

trieda Kontext {súkromná stratégia stratégie; public Context (Strategy strategy) {setStrategy (strategy); } public void executeStrategy (int x) {strategy.execute (x); } public void setStrategy (Strategy strategy) {this.strategy = strategy; }}

The Kontext trieda v zozname 4 ukladá stratégiu, keď je vytvorená, poskytuje metódu na následnú zmenu stratégie a poskytuje ďalšiu metódu na vykonanie aktuálnej stratégie. Okrem odovzdania stratégie konštruktérovi je tento vzor viditeľný v triede java.awt .Container, ktorej void setLayout (LayoutManager mgr) a void doLayout () metódy určujú a vykonávajú stratégiu manažéra rozloženia.

StrategyDemo

Potrebujeme klienta, aby predviedol predchádzajúce typy. Zoznam 5 darčekov a StrategyDemo klientská trieda.

Zoznam 5. StrategyDemo

verejná trieda StrategyDemo {public static void main (String [] args) {Context context = new Context (new ConcreteStrategyA ()); context.executeStrategy (1); context.setStrategy (nový ConcreteStrategyB ()); context.executeStrategy (2); }}

Konkrétna stratégia je spojená s a Kontext napríklad pri vytváraní kontextu. Stratégiu je možné následne zmeniť prostredníctvom volania kontextovej metódy.

Ak tieto triedy zostavíte a spustíte StrategyDemo, mali by ste dodržiavať nasledujúci výstup:

vykonávanie stratégie A: x = 1 vykonávanie stratégie B: x = 2

Prehodnotenie vzoru návštevníkov

Návštevník je konečný vzor návrhu softvéru, ktorý sa objaví v Dizajnové vzory. Aj keď je tento vzor správania v knihe uvedený ako posledný z abecedných dôvodov, niektorí sa domnievajú, že by mal kvôli svojej zložitosti zostať na poslednom mieste. Nováčikovia v službe Visitor často zápasia s týmto vzorom návrhu softvéru.

Ako je vysvetlené v Dizajnové vzory, návštevník vám umožňuje pridávať operácie do tried bez ich zmeny, trochu kúzla, ktoré uľahčuje takzvaná technika dvojitého odoslania. Aby sme pochopili vzor Návštevník, musíme najskôr stráviť dvojité odoslanie.

Čo je to dvojité odoslanie?

Java a mnoho ďalších jazykov podporuje polymorfizmus (veľa tvarov) pomocou techniky známej ako dynamické odoslanie, v ktorom je správa za behu programu namapovaná na konkrétnu sekvenciu kódu. Dynamické odoslanie sa klasifikuje ako jedno odoslanie alebo viacnásobné odoslanie:

  • Jedno odoslanie: Vzhľadom na hierarchiu tried, v ktorej každá trieda implementuje rovnakú metódu (to znamená, že každá podtrieda má prednosť pred verziou metódy v predchádzajúcej triede), a vzhľadom na premennú, ktorej je priradená inštancia jednej z týchto tried, je možné typ zistiť iba na adrese beh programu. Predpokladajme napríklad, že každá trieda implementuje metódu print (). Predpokladajme tiež, že jedna z týchto tried je vytvorená za behu aplikácie a jej premenná je priradená k premennej a. Keď sa stretne kompilátor Java a.print ();, môže to iba overiť atyp obsahuje a print () metóda. Nevie, ktorý spôsob má zavolať. Za behu virtuálny stroj skúma referenciu v premennej a a zistí skutočný typ, aby bolo možné zavolať správnu metódu. Táto situácia, v ktorej je implementácia založená na jednom type (type inštancie), je známa ako jednorazové odoslanie.
  • Hromadné odoslanie: Na rozdiel od jednorazového odoslania, kde jediný argument určuje, ktorú metódu tohto mena treba vyvolať, hromadné odoslanie používa všetky svoje argumenty. Inými slovami, zovšeobecňuje dynamické odosielanie na prácu s dvoma alebo viacerými objektmi. (Upozorňujeme, že argument v jedinom odoslaní je zvyčajne špecifikovaný oddeľovačom období naľavo od volaného názvu metódy, napríklad a v a.print ().)

Nakoniec dvojité odoslanie je zvláštny prípad hromadného odoslania, v ktorom sú do hovoru zapojené runtime typy dvoch objektov. Aj keď Java podporuje jednorazové odoslanie, priamo nepodporuje dvojité odoslanie. Môžeme to však simulovať.

Spoliehame sa príliš na dvojité odoslanie?

Blogger Derek Greer je presvedčený, že použitie dvojitého odoslania môže naznačovať problém s dizajnom, ktorý by mohol mať vplyv na udržiavateľnosť aplikácie. Podrobnosti si prečítajte v blogovom príspevku Greera „Dvojitá expedícia je vôňa kódu“ a súvisiace komentáre.

Simulácia dvojitého odoslania v kóde Java

Záznam Wikipédie pri dvojitom odoslaní poskytuje príklad založený na C ++, ktorý ukazuje, že je viac ako preťaženie funkciami. V zozname 6 uvádzam ekvivalent Java.

Výpis 6. Dvojité odoslanie v kóde Java

public class DDDemo {public static void main (String [] args) {Asteroid theAsteroid = nový Asteroid (); SpaceShip theSpaceShip = nový SpaceShip (); ApolloSpacecraft theApolloSpacecraft = nový ApolloSpacecraft (); theAsteroid.collideWith (theSpaceShip); theAsteroid.collideWith (theApolloSpacecraft); System.out.println (); ExplodingAsteroid theExplodingAsteroid = nový ExplodingAsteroid (); theExplodingAsteroid.collideWith (theSpaceShip); theExplodingAsteroid.collideWith (theApolloSpacecraft); System.out.println (); Asteroid theAsteroidReference = theExplodingAsteroid; theAsteroidReference.collideWith (theSpaceShip); theAsteroidReference.collideWith (theApolloSpacecraft); System.out.println (); SpaceShip theSpaceShipReference = theApolloSpacecraft; theAsteroid.collideWith (theSpaceShipReference); theAsteroidReference.collideWith (theSpaceShipReference); System.out.println (); theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith (theAsteroid); theSpaceShipReference.collideWith (theAsteroidReference); }} trieda SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} trieda ApolloSpacecraft rozširuje SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} trieda Asteroid {void collideWith (SpaceShip s) {System.out.println ("Asteroid narazil na SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Asteroid zasiahol ApolloSpacecraft"); }} trieda ExplodingAsteroid rozširuje Asteroid {void collideWith (SpaceShip s) {System.out.println ("ExplodingAsteroid narazil na SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("ExplodingAsteroid narazil na ApolloSpacecraft"); }}

Výpis 6 čo najpresnejšie sleduje svoj náprotivok v C ++. Posledné štyri riadky v hlavný() metóda spolu s void collideWith (Asteroid inAsteroid) metódy v Vesmírna loď a Kozmická loď Apollo demonštrovať a simulovať dvojité odoslanie.

Zvážte nasledujúci výňatok z konca roku 2006 hlavný():

theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith (theAsteroid); theSpaceShipReference.collideWith (theAsteroidReference);

Tretí a štvrtý riadok používajú na zistenie správnosti jediné odoslanie collideWith () metóda (v Vesmírna loď alebo Kozmická loď Apollo) dovolať sa. Toto rozhodnutie robí virtuálny stroj na základe typu referencie uloženej v theSpaceShipReference.

Zvnútra collideWith (), inAsteroid.collideWith (toto); používa jedinú expedíciu na zistenie správnej triedy (Asteroid alebo Explodujúci asteroid) obsahujúci požadované collideWith () metóda. Pretože Asteroid a Explodujúci asteroid preťaženie collideWith (), typ argumentu toto (Vesmírna loď alebo Kozmická loď Apollo) sa používa na rozlíšenie správneho collideWith () spôsob volania.

A tým sme dosiahli dvojité odoslanie. Aby sme to zhrnuli, najskôr sme zavolali collideWith () v Vesmírna loď alebo Kozmická loď Apollo, a potom použil svoj argument a toto zavolať jednému z collideWith () metódy v Asteroid alebo Explodujúci asteroid.

Keď bežíte DDDemo, mali by ste dodržiavať nasledujúci výstup:

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