Tolkemønster i Java

1. Oversigt

I denne vejledning introducerer vi et af de adfærdsmæssige GoF-designmønstre - Tolken.

Først giver vi et overblik over dets formål og forklarer det problem, det forsøger at løse.

Derefter ser vi på tolkens UML-diagram og implementeringen af ​​det praktiske eksempel.

2. Tolkens designmønster

Kort sagt mønsteret definerer grammatikken for et bestemt sprog på en objektorienteret måde, som kan evalueres af selve tolken.

Med det i tankerne kunne vi teknisk set bygge vores brugerdefinerede regulære udtryk, en brugerdefineret DSL-tolk, eller vi kunne analysere et hvilket som helst af de menneskelige sprog, bygge abstrakte syntaks træer og derefter køre fortolkningen.

Dette er kun nogle af de potentielle anvendelsestilfælde, men hvis vi tænker et stykke tid, kunne vi finde endnu flere anvendelser af det, for eksempel i vores IDE'er, da de løbende fortolker den kode, vi skriver, og dermed forsyner os med uvurderlige tip.

Tolkemønsteret skal generelt bruges, når grammatikken er relativt enkel.

Ellers kan det blive svært at vedligeholde.

3. UML-diagram

Ovenstående diagram viser to hovedenheder: Sammenhæng og Udtryk.

Nu skal ethvert sprog udtrykkes på en eller anden måde, og ordene (udtryk) vil have en eller anden betydning baseret på den givne kontekst.

Abstrakt Udtryk definerer en abstrakt metode, der tager kontekstensom en parameter. Tak til det, hvert udtryk påvirker konteksten, ændre dens tilstand og enten fortsætte fortolkningen eller returnere resultatet selv.

Derfor vil konteksten være indehaveren af ​​den globale behandlingstilstand, og den vil blive genbrugt under hele fortolkningsprocessen.

Så hvad er forskellen mellem TerminalExpression og NonTerminalExpression?

EN NonTerminalExpression kan have en eller flere andre Abstrakte udtryk forbundet med det, derfor kan det fortolkes rekursivt. Til sidst, fortolkningsprocessen skal slutte med et TerminalExpression der vil returnere resultatet.

Det er værd at bemærke det NonTerminalExpression er en sammensatte.

Endelig er klientens rolle at oprette eller bruge en allerede oprettet abstrakt syntaks træ, som ikke er mere end en sætning defineret i det oprettede sprog.

4. Implementering

For at vise mønsteret i aktion bygger vi en enkel SQL-lignende syntaks på en objektorienteret måde, som derefter fortolkes og returnerer os resultatet.

Først definerer vi Vælg, Fra, og Hvor udtryk, opbyg et syntaks træ i klientens klasse og kør fortolkningen.

Det Udtryk interface har fortolkningsmetoden:

Liste fortolke (Context ctx);

Dernæst definerer vi det første udtryk, Vælg klasse:

klasse Vælg implementerer Expression {privat streng-kolonne; privat Fra fra; // constructor @Override public List interpret (Context ctx) {ctx.setColumn (column); return from.interpret (ctx); }}

Det får kolonnenavnet til at blive valgt og en anden beton Udtryk af typen Fra som parametre i konstruktøren.

Bemærk, at i tilsidesat fortolke() metode indstiller kontekstens tilstand og videregiver fortolkningen videre til et andet udtryk sammen med konteksten.

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

Et andet udtryk er Fra klasse:

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

Nu, hvor SQL-klausulen er valgfri i SQL, er denne klasse derfor enten en terminal eller et ikke-terminal udtryk.

Hvis brugeren beslutter ikke at bruge en hvor-klausul, Fra udtryk, det vil blive afsluttet med ctx.search () ring og returner resultatet. Ellers fortolkes det yderligere.

Det Hvor udtryk ændrer igen konteksten ved at indstille det nødvendige filter og afslutter fortolkningen med søgekald:

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

For eksempel er Sammenhæng klasse holder de data, der efterligner databasetabellen.

Bemærk, at den har tre nøglefelter, som ændres af hver underklasse af Udtryk og søgemetoden:

klasse Kontekst {privat statisk kort tabeller = nyt HashMap (); statisk {Liste liste = ny ArrayList (); list.add (ny række ("John", "Doe")); list.add (ny række ("Jan", "Kowalski")); list.add (ny række ("Dominic", "Doom")); tables.put ("folk", liste); } privat strengbord; privat String 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 }}

Når søgningen er udført, rydder konteksten i sig selv, så kolonnen, tabellen og filteret er indstillet til standardindstillinger.

På den måde påvirker hver fortolkning ikke den anden.

5. Testning

Til testformål skal vi se 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 = forespørgsel. Fortolke (ctx); System.out.println (resultat); Udtryksforespørgsel2 = ny Vælg ("*", ny Fra ("folk")); Liste resultat2 = forespørgsel2.interpret (ctx); System.out.println (resultat2); Udtryksforespørgsel3 = ny Vælg ("navn", nyt Fra ("personer", nyt hvor (navn -> navn.tilLængerefase (). Starter med ("d")))); Liste resultat3 = forespørgsel3.interpret (ctx); System.out.println (result3); }}

Først bygger vi et syntakstræ med oprettede udtryk, initialiserer konteksten og kører derefter fortolkningen. Konteksten genbruges, men som vi viste ovenfor, renser den sig selv efter hvert søgekald.

Ved at køre programmet skal output være som følger:

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

6. Ulemper

Når grammatikken bliver mere kompleks, bliver det sværere at vedligeholde.

Det kan ses i det præsenterede eksempel. Det ville være med rimelighed let at tilføje et andet udtryk, ligesom Begrænse, alligevel vil det ikke være for let at vedligeholde, hvis vi beslutter at fortsætte med at udvide det med alle andre udtryk.

7. Konklusion

Tolkens designmønster er fantastisk til relativt enkel grammatikfortolkning, som ikke behøver at udvikle sig og strække sig meget.

I eksemplet ovenfor viste vi, at det er muligt at opbygge en SQL-lignende forespørgsel på en objektorienteret måde ved hjælp af tolkemønsteret.

Endelig kan du finde dette mønsterbrug i JDK, især i java.util.Mønster, java.text.Format eller java.text.Normalizer.

Som sædvanlig er den komplette kode tilgængelig på Github-projektet.


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