Tolkdesignmønster i Java

1. Oversikt

I denne veiledningen vil vi introdusere et av GoFs atferdsmønstre - tolk.

Først vil vi gi en oversikt over formålet og forklare problemet den prøver å løse.

Deretter tar vi en titt på UML-diagrammet til Tolk og implementeringen av det praktiske eksemplet.

2. Tolkdesignmønster

Kort sagt, mønsteret definerer grammatikken til et bestemt språk på en objektorientert måte som kan evalueres av tolk selv.

Med det i bakhodet, teknisk sett kan vi bygge vårt tilpassede regulære uttrykk, en tilpasset DSL-tolk eller vi kan analysere noen av menneskets språk, bygge abstrakte syntaksetrær og kjør deretter tolkningen.

Dette er bare noen av de potensielle brukssakene, men hvis vi tenker en stund, kan vi finne enda flere bruksområder av det, for eksempel i våre IDEer, siden de kontinuerlig tolker koden vi skriver og dermed forsyner oss med uvurderlige hint.

Tolkemønsteret bør generelt brukes når grammatikken er relativt enkel.

Ellers kan det bli vanskelig å vedlikeholde.

3. UML-diagram

Ovenstående diagram viser to hovedenheter: Kontekst og Uttrykk.

Nå må ethvert språk uttrykkes på en eller annen måte, og ordene (uttrykkene) vil ha noen betydning basert på den gitte konteksten.

Abstrakt Uttrykk definerer en abstrakt metode som tar kontekstensom parameter. Takket være det, hvert uttrykk vil påvirke konteksten, endre tilstand og enten fortsette tolkningen eller returnere resultatet selv.

Derfor kommer konteksten til å være innehaveren av den globale prosessen, og den vil bli gjenbrukt under hele tolkningsprosessen.

Så hva er forskjellen mellom TerminalExpression og NonTerminalExpression?

EN NonTerminalExpression kan ha en eller flere andre Abstrakte uttrykk assosiert i den, derfor kan den tolkes rekursivt. Til slutt, prosessen med tolkning må avsluttes med et TerminalExpression som vil gi resultatet.

Det er verdt å merke seg det NonTerminalExpression er en sammensatte.

Til slutt er klientens rolle å opprette eller bruke en allerede opprettet abstrakt syntaks tre, som ikke er mer enn en setning definert i det opprettede språket.

4. Gjennomføring

For å vise mønsteret i aksjon bygger vi en enkel SQL-lignende syntaks på en objektorientert måte, som deretter tolkes og gir oss resultatet.

Først definerer vi Velg, Fra, og Hvor uttrykk, bygg et syntaks-tre i klientens klasse og kjør tolkningen.

De Uttrykk grensesnittet vil ha tolke metoden:

List interpret (Context ctx);

Deretter definerer vi det første uttrykket, Å velge klasse:

klasse Velg implementerer uttrykk {privat strengkolonne; privat Fra fra; // constructor @Override public List interpret (Context ctx) {ctx.setColumn (column); return from.interpret (ctx); }}

Det får kolonnenavnet som skal velges og en annen betong Uttrykk av typen Fra som parametere i konstruktøren.

Merk at i overstyrt tolke() metode setter den tilstanden til konteksten og overfører tolkningen videre til et annet uttrykk sammen med konteksten.

På den måten ser vi at det er en NonTerminalExpression.

Et annet uttrykk er Fra klasse:

klasse Fra redskaper Uttrykk {privat strengbord; privat Hvor hvor; // constructors @Override public List interpret (Context ctx) {ctx.setTable (table); hvis (hvor == null) {return ctx.search (); } returner hvor.interpret (ctx); }}

Nå, i SQL hvor setningen er valgfri, er denne klassen enten et terminal- eller et ikke-terminaluttrykk.

Hvis brukeren bestemmer seg for ikke å bruke en hvor-klausul, blir Fra uttrykk det vil bli avsluttet med ctx.search () ring og returner resultatet. Ellers blir det tolket videre.

De Hvor uttrykk endrer igjen konteksten ved å sette det nødvendige filteret og avslutter tolkningen med søkeanrop:

klasse Hvor implementerer Expression {private Predicate filter; // constructor @Override public List interpret (Context ctx) {ctx.setFilter (filter); returner ctx.search (); }}

For eksempel er Kontekst klasse holder dataene som etterligner databasetabellen.

Merk at den har tre nøkkelfelt som endres av hver underklasse av Uttrykk og søkemetoden:

klasse Kontekst {privat statisk kart tabeller = ny HashMap (); statisk {Listeliste = ny ArrayList (); list.add (ny rad ("John", "Doe")); list.add (ny rad ("Jan", "Kowalski")); list.add (ny rad ("Dominic", "Doom")); tables.put ("folk", liste); } privat strengbord; privat streng kolonne; private Predicate 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 ()); klar(); returresultat; }}

Etter at søket er gjort, rydder konteksten i seg selv, så kolonnen, tabellen og filteret er satt til standardinnstillinger.

På den måten vil hver tolkning ikke påvirke den andre.

5. Testing

For testformål, la oss ta en titt på Tolk Demo klasse:

public class InterpreterDemo {public static void main (String [] args) {Expression query = new Select ("name", new From ("people")); Kontekst ctx = ny kontekst (); Listeresultat = spørring.interpret (ctx); System.out.println (resultat); Uttrykk query2 = ny Velg ("*", ny Fra ("folk")); Liste resultat2 = spørring2.interpret (ctx); System.out.println (result2); Uttrykk query3 = ny Velg ("navn", ny Fra ("folk", ny Hvor (navn -> navn.tilLowerCase (). StarterMed ("d")))); Liste resultat3 = spørring3.interpret (ctx); System.out.println (result3); }}

Først bygger vi et syntakstreet med opprettet uttrykk, initialiserer konteksten og kjører deretter tolkningen. Konteksten blir gjenbrukt, men som vi viste ovenfor, renser den seg selv etter hvert søkeanrop.

Ved å kjøre programmet skal utgangen være som følger:

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

6. Ulemper

Når grammatikken blir mer kompleks, blir den vanskeligere å vedlikeholde.

Det kan sees i det presenterte eksemplet. Det ville være rimelig enkelt å legge til et annet uttrykk, som Grense, men det vil ikke være for lett å vedlikeholde hvis vi bestemmer oss for å fortsette å utvide det med alle andre uttrykk.

7. Konklusjon

Tolkens designmønster er flott for relativt enkel grammatikkfortolkning, som ikke trenger å utvikle seg og utvide seg mye.

I eksemplet ovenfor viste vi at det er mulig å bygge et SQL-lignende spørsmål på en objektorientert måte ved hjelp av tolkemønsteret.

Endelig kan du finne denne mønsterbruken i JDK, spesielt i java.util.Mønster, java.text.Format eller java.text.Normalizer.

Som vanlig er den komplette koden tilgjengelig på Github-prosjektet.


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