Programování

Úvod do návrhových vzorů, Část 2: Znovu navštívená klasika čtyř gangů

V části 1 této třídílné série představující designové vzory jsem se zmínil Designové vzory: Prvky opakovaně použitelného objektově orientovaného designu. Tuto klasiku napsali Erich Gamma, Richard Helm, Ralph Johnson a John Vlissides, kteří byli souhrnně známí jako Gang čtyř. Jak většina čtenářů ví, Designové vzory představuje 23 návrhových vzorů softwaru, které zapadají do kategorií diskutovaných v části 1: Tvorba, struktura a chování.

Návrhové vzory v prostředí JavaWorld

Série návrhových vzorů Java od Davida Gearyho je mistrovským úvodem k mnoha vzorům Gang of Four v kódu Java.

Designové vzory je kanonické čtení pro vývojáře softwaru, ale mnoho nových programátorů čelí jeho referenčnímu formátu a rozsahu. Každý z 23 vzorů je podrobně popsán ve formátu šablony skládající se z 13 sekcí, které mohou být hodně strávitelné. Další výzvou pro nové vývojáře Java je, že vzory Gang of Four vycházejí z objektově orientovaného programování, s příklady založenými na C ++ a Smalltalk, ne na Java kódu.

V tomto tutoriálu rozbalím dva z běžně používaných vzorů - Strategy a Visitor - z pohledu vývojáře Java. Strategie je poměrně jednoduchý vzor, ​​který slouží jako příklad toho, jak si můžete namočit nohy pomocí návrhových vzorů GoF obecně; Návštěvník je komplexnější a středně pokročilý. Začnu příkladem, který by měl demystifikovat mechanismus dvojitého odeslání, který je důležitou součástí vzoru návštěvníka. Pak předvedu vzor návštěvníka v případu použití kompilátoru.

Následující moje příklady by vám měly pomoci prozkoumat a použít ostatní vzory GoF pro sebe. Kromě toho nabídnu tipy, jak z knihy Gang čtyř čtenářů vytěžit maximum, a na závěr shrnu kritiku použití návrhových vzorů při vývoji softwaru. Tato diskuse by mohla být obzvláště relevantní pro vývojáře, kteří začínají s programováním.

Strategie vybalení

The Strategie pattern vám umožňuje definovat rodinu algoritmů, jako jsou algoritmy používané pro třídění, složení textu nebo správu rozvržení. Strategie také umožňuje zapouzdřit každý algoritmus do jeho vlastní třídy a zajistit jejich zaměnitelnost. Každý zapouzdřený algoritmus je znám jako strategie. Za běhu si klient vybere vhodný algoritmus pro své požadavky.

Co je klient?

A klient je jakýkoli software, který interaguje s návrhovým vzorem. I když je to obvykle objekt, může být klient také kódem v rámci aplikace public static void main (String [] args) metoda.

Na rozdíl od vzoru Decorator, který se zaměřuje na změnu objektu kůže, nebo vzhled, Strategie se zaměřuje na změnu objektu vnitřnosti, což znamená jeho proměnlivé chování. Strategie vám umožní vyhnout se používání více podmíněných příkazů přesunutím podmíněných větví do jejich vlastních strategických tříd. Tyto třídy často pocházejí z abstraktní nadtřídy, na kterou klient odkazuje a používá ji k interakci s konkrétní strategií.

Z abstraktního pohledu zahrnuje Strategie Strategie, ConcreteStrategyX, a Kontext typy.

Strategie

Strategie poskytuje společné rozhraní pro všechny podporované algoritmy. Výpis 1 představuje soubor Strategie rozhraní.

Výpis 1. void execute (int x) musí být implementován všemi konkrétními strategiemi

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

Pokud konkrétní strategie nejsou parametrizovány běžnými daty, můžete je implementovat prostřednictvím prostředí Java rozhraní Vlastnosti. Tam, kde jsou parametrizovány, byste místo toho deklarovali abstraktní třídu. Například strategie zarovnání doprava, zarovnání na střed a zarovnání textu sdílejí koncept a šířka ve kterém provést zarovnání textu. Toto byste tedy prohlásili šířka v abstraktní třídě.

ConcreteStrategyX

Každý ConcreteStrategyX implementuje společné rozhraní a poskytuje implementaci algoritmu. Výpis 2 implementuje výpisy 1 Strategie rozhraní k popisu konkrétní konkrétní strategie.

Výpis 2. ConcreteStrategyA provede jeden algoritmus

veřejná třída ConcreteStrategyA implementuje strategii {@Override public void execute (int x) {System.out.println ("prováděcí strategie A: x =" + x); }}

The void execute (int x) metoda v seznamu 2 určuje konkrétní strategii. Představte si tuto metodu jako abstrakci něčeho užitečnějšího, jako je určitý druh třídicího algoritmu (např. Bubble Sort, Insertion Sort nebo Quick Sort), nebo konkrétní druh správce rozvržení (např. Flow Layout, Border Layout nebo Rozvržení mřížky).

Výpis 3 představuje sekundu Strategie implementace.

Výpis 3. ConcreteStrategyB provede další algoritmus

veřejná třída ConcreteStrategyB implementuje strategii {@Override public void execute (int x) {System.out.println ("prováděcí strategie B: x =" + x); }}

Kontext

Kontext poskytuje kontext, ve kterém je vyvolána konkrétní strategie. Výpisy 2 a 3 zobrazují data předávaná z kontextu do strategie prostřednictvím parametru metody. Protože generické strategické rozhraní sdílí všechny konkrétní strategie, některé z nich nemusí vyžadovat všechny parametry. Abyste se vyhnuli zbytečným parametrům (zejména při předávání mnoha různých druhů argumentů pouze několika konkrétním strategiím), můžete místo toho předat odkaz na kontext.

Místo toho, abyste předali kontextový odkaz na metodu, můžete ji uložit do abstraktní třídy, takže vaše metoda volá bez parametrů. V kontextu by však bylo nutné určit rozsáhlejší rozhraní, které by zahrnovalo smlouvu o přístupu k kontextovým datům jednotným způsobem. Výsledkem, jak je uvedeno v seznamu 4, je těsnější propojení strategií a jejich kontextu.

Výpis 4. Kontext je konfigurován pomocí instance ConcreteStrategyx

třída Kontext {soukromá strategická strategie; public Context (Strategy strategy) {setStrategy (strategy); } public void executeStrategy (int x) {strategy.execute (x); } public void setStrategy (Strategy strategy) {this.strategy = strategy; }}

The Kontext třída v seznamu 4 ukládá strategii, když je vytvořena, poskytuje metodu pro následnou změnu strategie a poskytuje další metodu k provedení aktuální strategie. Kromě předání strategie konstruktoru lze tento vzor vidět ve třídě java.awt .Container, jejíž void setLayout (LayoutManager mgr) a void doLayout () metody určují a provádějí strategii správce rozvržení.

StrategyDemo

Potřebujeme klienta, aby předvedl předchozí typy. Výpis 5 představuje a StrategyDemo třída klienta.

Výpis 5. StrategyDemo

veřejná třída StrategyDemo {public static void main (String [] args) {Context context = new Context (new ConcreteStrategyA ()); context.executeStrategy (1); context.setStrategy (nový ConcreteStrategyB ()); context.executeStrategy (2); }}

Konkrétní strategie je spojena s a Kontext instance při vytváření kontextu. Strategii lze následně změnit pomocí volání kontextové metody.

Pokud tyto třídy sestavíte a spustíte StrategyDemo, měli byste dodržovat následující výstup:

provádění strategie A: x = 1 provádění strategie B: x = 2

Přehodnocení vzoru návštěvníka

Návštěvník je konečný vzor návrhu softwaru, který se objeví v Designové vzory. Ačkoli je tento vzor chování uveden v knize jako poslední z abecedních důvodů, někteří věří, že by kvůli své složitosti měl přijít jako poslední. Noví návštěvníci návštěvníka často zápasí s tímto vzorem softwarového designu.

Jak je vysvětleno v Designové vzory, návštěvník vám umožňuje přidávat operace do tříd, aniž byste je měnili, trochu kouzla, které usnadňuje takzvaná technika dvojitého odeslání. Abychom pochopili vzor návštěvníka, musíme nejprve strávit dvojité odeslání.

Co je to dvojité odeslání?

Java a mnoho dalších jazyků podporuje polymorfismus (mnoho tvarů) pomocí techniky známé jako dynamické odesílání, ve kterém je zpráva za běhu mapována na konkrétní sekvenci kódu. Dynamické odeslání je klasifikováno jako jedno odeslání nebo více odeslání:

  • Jedna expedice: Vzhledem k hierarchii tříd, kde každá třída implementuje stejnou metodu (to znamená, že každá podtřída přepisuje verzi metody předchozí třídy), a vzhledem k proměnné, které je přiřazena instance jedné z těchto tříd, lze typ zjistit pouze na runtime. Předpokládejme například, že každá třída implementuje metodu tisk(). Předpokládejme také, že jedna z těchto tříd je vytvořena za běhu a její proměnná je přiřazena proměnné A. Když narazí kompilátor Java a.print ();, může to pouze ověřit Atyp obsahuje a tisk() metoda. Neví, kterou metodu volat. Za běhu virtuální stroj zkoumá odkaz v proměnné A a zjistí skutečný typ, aby bylo možné volat správnou metodu. Tato situace, ve které je implementace založena na jediném typu (typu instance), je známá jako jediné odeslání.
  • Vícenásobné odeslání: Na rozdíl od jednoho odeslání, kde jediný argument určuje, kterou metodu tohoto jména vyvolat, hromadné odeslání používá všechny své argumenty. Jinými slovy generalizuje dynamické odesílání pro práci se dvěma nebo více objekty. (Všimněte si, že argument v jediném odeslání je obvykle zadán s oddělovačem období nalevo od názvu metody, která se volá, například A v a.print ().)

Konečně, dvojité odeslání je speciální případ vícenásobného odeslání, ve kterém jsou do volání zapojeny runtime typy dvou objektů. Ačkoli Java podporuje jednu expedici, nepodporuje přímo dvojitou expedici. Ale můžeme to simulovat.

Spoléháme příliš na dvojité odeslání?

Blogger Derek Greer věří, že použití dvojitého odeslání může znamenat problém s designem, který by mohl ovlivnit udržovatelnost aplikace. Podrobnosti najdete v blogovém příspěvku Greera „Dvojí odeslání je vůně kódu“ a přidružené komentáře.

Simulace dvojitého odeslání v kódu Java

Záznam Wikipedie při dvojitém odeslání poskytuje příklad založený na C ++, který ukazuje, že je více než přetížení funkcí. V seznamu 6 uvádím ekvivalent Java.

Výpis 6. Dvojité odeslání v kódu 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); }} třída SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} třída ApolloSpacecraft rozšiřuje SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} třída Asteroid {void collideWith (SpaceShip s) {System.out.println ("Asteroid zasáhl SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Asteroid zasáhl ApolloSpacecraft"); }} třída ExplodingAsteroid rozšiřuje Asteroid {void collideWith (SpaceShip s) {System.out.println ("ExplodingAsteroid zasáhl SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("ExplodingAsteroid zasáhl ApolloSpacecraft"); }}

Výpis 6 co nejpřesněji sleduje svůj protějšek v C ++. Poslední čtyři řádky v hlavní() metoda spolu s void collideWith (Asteroid inAsteroid) metody v Kosmická loď a Kosmická loď Apollo předvést a simulovat dvojí odeslání.

Zvažte následující výňatek z konce roku 2006 hlavní():

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

Třetí a čtvrtý řádek používají jednotné odeslání k určení správnosti srazit se() metoda (v Kosmická loď nebo Kosmická loď Apollo) vyvolat. Toto rozhodnutí provádí virtuální stroj na základě typu reference uložené v theSpaceShipReference.

Zevnitř srazit se(), inAsteroid.collideWith (toto); používá jedinou expedici k určení správné třídy (Asteroid nebo Explodující asteroid) obsahující požadované srazit se() metoda. Protože Asteroid a Explodující asteroid přetížení srazit se(), typ argumentu tento (Kosmická loď nebo Kosmická loď Apollo) se používá k rozlišení správného srazit se() způsob volání.

A tím jsme dosáhli dvojího odeslání. Abychom to shrnuli, nejprve jsme volali srazit se() v Kosmická loď nebo Kosmická loď Apollo, a poté použil svůj argument a tento zavolat jednomu z srazit se() metody v Asteroid nebo Explodující asteroid.

Když běžíš DDDemo, měli byste dodržovat následující výstup: