Návrhový vzor tlmočníka v Jave

1. Prehľad

V tomto tutoriáli si predstavíme jeden z návrhových vzorov správania GoF - tlmočník.

Spočiatku poskytneme prehľad jeho účelu a vysvetlíme problém, ktorý sa snaží vyriešiť.

Potom sa pozrieme na UML diagram tlmočníka a implementáciu praktického príkladu.

2. Návrhový vzor tlmočníka

Skrátka vzor definuje gramatiku konkrétneho jazyka objektovo orientovaným spôsobom, ktorý dokáže vyhodnotiť samotný tlmočník.

Berúc do úvahy toto, technicky by sme mohli vytvoriť náš vlastný regulárny výraz, vlastného tlmočníka DSL alebo sme mohli analyzovať akýkoľvek ľudský jazyk, zostavte abstraktné syntaxové stromy a potom spustite interpretáciu.

Toto je iba niekoľko potenciálnych prípadov použitia, ale ak chvíľu premýšľame, mohli by sme nájsť ešte viac jeho využití, napríklad v našich IDE, pretože neustále interpretujú kód, ktorý píšeme, a tým nám dodávajú neoceniteľné rady.

Vzor tlmočníka by sa mal všeobecne používať, keď je gramatika pomerne jednoduchá.

Inak by mohlo byť ťažké udržiavať ho.

3. Diagram UML

Vyššie uvedený diagram zobrazuje dve hlavné entity: Kontext a Vyjadrenie.

Akýkoľvek jazyk musí byť teraz vyjadrený nejakým spôsobom a slová (výrazy) budú mať určitý význam na základe daného kontextu.

AbstractExpression definuje jednu abstraktnú metódu, ktorá berie kontextako parameter. Vďaka tomu každý výraz ovplyvní kontext, zmeňte jeho stav a pokračujte vo výklade, alebo vráťte samotný výsledok.

Kontext bude teda držiteľom globálneho stavu spracovania a bude sa opakovane používať počas celého procesu tlmočenia.

Aký je teda rozdiel medzi TerminalExpression a NonTerminalExpression?

A NonTerminalExpression môže mať jeden alebo viac ďalších Abstraktné výrazy v ňom spojené, preto ho možno rekurzívne interpretovať. Na koniec, proces tlmočenia musí skončiť Terminálový výraz tým sa vráti výsledok.

Je potrebné si to uvedomiť NonTerminalExpression je a zložený.

Nakoniec je úlohou klienta vytvoriť alebo použiť už vytvorený abstraktný strom syntaxe, čo nie je nič iné ako a veta definovaná vo vytvorenom jazyku.

4. Implementácia

Aby sme ukázali vzor v akcii, zostavíme jednoduchú syntax podobnú SQL objektovo orientovaným spôsobom, ktorá sa potom interpretuje a vráti nám výsledok.

Najprv definujeme Vyberte, Od, a Kde výrazy, zostavte syntaxový strom v triede klienta a spustite interpretáciu.

The Vyjadrenie rozhranie bude mať metódu interpretácie:

List interpret (Context ctx);

Ďalej definujeme prvý výraz, Vyberte trieda:

class Select implements Expression {private String column; súkromné ​​z od; // konštruktor @Override public List interpret (Context ctx) {ctx.setColumn (stĺpec); návrat z.interpret (ctx); }}

Získava názov stĺpca, ktorý sa má vybrať, a ďalší konkrétny Vyjadrenie typu Od ako parametre v konštruktore.

Všimnite si, že v prepísanom tlmočiť () metóda nastavuje stav kontextu a prenáša interpretáciu ďalej na iný výraz spolu s kontextom.

Týmto spôsobom vidíme, že je to NonTerminalExpression.

Ďalším výrazom je Od trieda:

trieda Z implementuje Expression {private String table; súkromné ​​Kde kde; // konštruktory @Override public List interpret (Context ctx) {ctx.setTable (table); if (where == null) {return ctx.search (); } návrat kde.interpret (ctx); }}

Teraz je v SQL klauzula where voliteľná, preto je táto trieda buď terminálnym alebo nekoncovým výrazom.

Ak sa užívateľ rozhodne nepoužívať klauzulu where, Od výraz bude ukončený s ctx.search () zavolajte a vráťte výsledok. Inak sa bude ďalej interpretovať.

The Kde výraz opäť upravuje kontext nastavením potrebného filtra a ukončí interpretáciu vyhľadávacím volaním:

trieda Kde implementuje Expression {private Predicate filter; // konštruktor @Override verejný zoznam interpret (kontext ctx) {ctx.setFilter (filter); návrat ctx.search (); }}

Napríklad Kontext trieda obsahuje údaje, ktoré napodobňujú databázovú tabuľku.

Všimnite si, že má tri kľúčové polia, ktoré sú upravené každou podtriedou Vyjadrenie a metóda vyhľadávania:

trieda Kontext {súkromná statická mapa tabuľky = nový HashMap (); static {List list = new ArrayList (); list.add (nový riadok ("John", "Doe")); list.add (nový riadok ("Jan", "Kowalski")); list.add (nový riadok ("Dominic", "Doom")); tables.put ("people", list); } private String table; súkromný stĺpec String; súkromný predikát whereFilter; // ... List search () {List result = tables.entrySet () .stream () .filter (entry -> entry.getKey (). EqualsIgnoreCase (table)) .flatMap (entry -> Stream.of (entry) .getValue ())) .flatMap (Collection :: stream) .map (Row :: toString) .flatMap (columnMapper) .filter (whereFilter) .collect (Collectors.toList ()); jasný(); návratový výsledok; }}

Po dokončení vyhľadávania sa kontext sám vyčistí, takže stĺpec, tabuľka a filter sú predvolené.

Takto nebude mať každá interpretácia vplyv na druhú.

5. Testovanie

Na účely testovania sa pozrime na TlmočníkDemo trieda:

public class InterpreterDemo {public static void main (String [] args) {Expression query = new Select ("name", new From ("people")); Kontext ctx = nový kontext (); Výsledok zoznamu = query.interpret (ctx); System.out.println (výsledok); Výraz query2 = new Select ("*", new From ("people")); Vypísať výsledok2 = query2.interpret (ctx); System.out.println (výsledok2); Výraz výraz3 = nový Vyberte ("meno", nový Od ("ľudia", nový Kde (meno -> meno.toLowerCase (). ZačínaWith ("d")))); Vypísať výsledok3 = query3.interpret (ctx); System.out.println (result3); }}

Najskôr zostavíme syntaxový strom s vytvorenými výrazmi, inicializujeme kontext a potom spustíme interpretáciu. Kontext sa znova používa, ale ako sme si ukázali vyššie, po každom vyhľadávacom hovore sa vyčistí.

Spustením programu by mal byť výstup nasledovný:

[John, Jan, Dominic] [John Doe, Jan Kowalski, Dominic Doom] [Dominic]

6. Nevýhody

Keď je gramatika čoraz zložitejšia, jej dodržiavanie je čoraz ťažšie.

Je to vidieť na predloženom príklade. Bolo by primerane ľahké pridať ďalší výraz, napríklad Obmedziť, ale nebude to príliš ľahké udržiavať, ak by sme sa rozhodli pokračovať v jeho rozširovaní o všetky ostatné výrazy.

7. Záver

Dizajn tlmočníka je vynikajúci za pomerne jednoduchú gramatickú interpretáciu, ktorý sa nemusí veľmi vyvíjať a rozširovať.

Vo vyššie uvedenom príklade sme preukázali, že je možné zostaviť dotaz typu SQL objektovo orientovaným spôsobom pomocou vzoru tlmočníka.

Nakoniec môžete nájsť toto použitie vzorov v JDK, najmä v java.util.Vzorok, java.text.Formát alebo java.text.Normalizer.

Kompletný kód je ako obvykle k dispozícii v projekte Github.


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