Uzorak dizajna tumača na Javi

1. Pregled

U ovom uputstvu predstavit ćemo jedan od obrazaca ponašanja GoF-a - Interpreter.

Isprva ćemo dati pregled njegove svrhe i objasniti problem koji pokušava riješiti.

Zatim ćemo pogledati UML dijagram tumača i provedbu praktičnog primjera.

2. Obrazac dizajna tumača

Ukratko, obrazac definira gramatiku određenog jezika na objektno orijentirani način koji tumač može procijeniti sam.

Imajući to na umu, tehnički bismo mogli izraditi svoj prilagođeni regularni izraz, prilagođeni DSL tumač ili bismo mogli raščlaniti bilo koji od ljudskih jezika, izgraditi stabla apstraktne sintakse i zatim pokrenuti interpretaciju.

Ovo su samo neki od mogućih slučajeva upotrebe, ali ako malo razmislimo, mogli bismo pronaći još više njegovih upotreba, na primjer u našim IDE-ima, jer oni neprestano tumače kod koji pišemo i tako nas opskrbljuju neprocjenjivi nagovještaji.

Obrazac tumača općenito treba koristiti kada je gramatika relativno jednostavna.

Inače bi moglo postati teško održavati.

3. UML dijagram

Gornji dijagram prikazuje dva glavna entiteta: Kontekst i Izraz.

Sad, svaki jezik treba biti izražen na neki način, a riječi (izrazi) će imati neko značenje temeljeno na danom kontekstu.

AbstractExpression definira jednu apstraktnu metodu koja uzima kontekstkao parametar. Zahvaljujući tome, svaki će izraz utjecati na kontekst, promijenite njegovo stanje i nastavite interpretaciju ili vratite sam rezultat.

Stoga će kontekst biti nositelj globalnog stanja obrade i ponovno će se koristiti tijekom cijelog postupka interpretacije.

Dakle, koja je razlika između TerminalExpression i NonTerminalExpression?

A NonTerminalExpression mogu imati jedno ili više drugih AbstractExpressions pridružen u njemu, stoga se može rekurzivno tumačiti. Na kraju, postupak tumačenja mora završiti s izraz TerminalExpression to će vratiti rezultat.

Vrijedno je to napomenuti NonTerminalExpression je kompozitni.

Konačno, uloga klijenta je stvoriti ili koristiti već stvoreno apstraktno stablo sintakse, što nije ništa više od a rečenica definirana u stvorenom jeziku.

4. Provedba

Da bismo prikazali obrazac na djelu, izgradit ćemo jednostavnu sintaksu nalik SQL-u na objektno orijentirani način, koji će se zatim protumačiti i vratiti nam rezultat.

Prvo ćemo definirati Odaberite, Od, i Gdje izraze, izradite stablo sintakse u klijentovoj klasi i pokrenite interpretaciju.

The Izraz sučelje će imati metodu interpretacije:

Tumačenje popisa (Context ctx);

Dalje, definiramo prvi izraz, Odaberi razred:

class Select implementira Expression {private String column; privatno From od; // konstruktor @Override javni popis tumačenja (Kontekst ctx) {ctx.setColumn (stupac); povratak iz.interpret (ctx); }}

Dobiva odabrani naziv stupca i još jedan konkretan Izraz tipa Iz kao parametri u konstruktoru.

Imajte na umu da u nadjačanom protumačiti () metoda postavlja stanje konteksta i prenosi interpretaciju dalje na drugi izraz zajedno s kontekstom.

Na taj način vidimo da je to a NonTerminalExpression.

Drugi izraz je Iz razred:

class From implementira Expression {private String table; privatno Gdje gdje; // konstruktori @Override javni popis interpretacija (Kontekst ctx) {ctx.setTable (tablica); if (gdje == null) {return ctx.search (); } return where.interpret (ctx); }}

Sada je u SQL-u klauzula where neobavezna, stoga je ova klasa ili terminal ili neterminalni izraz.

Ako korisnik odluči ne koristiti klauzulu where, Iz izraz će se prekinuti s ctx.search () nazovite i vratite rezultat. Inače, to će se dalje tumačiti.

The Gdje izraz ponovno modificira kontekst postavljanjem potrebnog filtra i završava tumačenje pozivom za pretraživanje:

klasa Gdje implementira Expression {privatni filtar predikata; // konstruktor @Override javni popis interpretacija (kontekst ctx) {ctx.setFilter (filter); povratak ctx.search (); }}

Na primjer, Kontekst klasa sadrži podatke koji imitiraju tablicu baze podataka.

Imajte na umu da ima tri ključna polja koja su modificirana u svakom podrazredu Izraz i način pretraživanja:

klasa Kontekst {privatna statička karta tablice = nova HashMap (); static {Popis popisa = novi ArrayList (); list.add (novi red ("John", "Doe")); list.add (novi red ("Jan", "Kowalski")); list.add (novi red ("Dominic", "Doom")); table.put ("ljudi", popis); } privatna niska tablica; privatni stupac String; privatni predikat whereFilter; // ... Lista popisa () {Rezultat popisa = table.entrySet () .stream () .filter (entry -> entry.getKey (). EqualsIgnoreCase (tablica)) .flatMap (entry -> Stream.of (entry .getValue ())) .flatMap (Collection :: stream) .map (Row :: toString) .flatMap (columnMapper) .filter (whereFilter) .collect (Collectors.toList ()); čisto(); povratni rezultat; }}

Nakon završetka pretraživanja kontekst se čisti, pa su stupac, tablica i filtar postavljeni na zadane vrijednosti.

Na taj način svako tumačenje neće utjecati na drugo.

5. Ispitivanje

Za potrebe testiranja, pogledajmo InterpreterDemo razred:

javna klasa InterpreterDemo {javna statička void glavna (String [] args) {Upit izraza = novo Odaberi ("ime", novo Od ("ljudi")); Kontekst ctx = novi kontekst (); Rezultat popisa = query.interpret (ctx); System.out.println (rezultat); Izraz query2 = new Select ("*", new From ("people")); Popis rezultata2 = query2.interpret (ctx); System.out.println (rezultat2); Izraz query3 = new Select ("name", new From ("people", new Where (name -> name.toLowerCase (). StartWith ("d")))); Popis rezultata3 = upit3.interpret (ctx); System.out.println (rezultat3); }}

Prvo gradimo sintaksno stablo sa stvorenim izrazima, inicijaliziramo kontekst i zatim pokrećemo interpretaciju. Kontekst se ponovno koristi, ali kao što smo gore pokazali, on se čisti nakon svakog poziva za pretraživanje.

Pokretanjem programa, izlaz bi trebao biti sljedeći:

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

6. Loše strane

Kad gramatika postaje složenija, postaje je teže održavati.

To se može vidjeti na prikazanom primjeru. Bilo bi razumno lako dodati još jedan izraz, poput Ograničiti, no to neće biti previše jednostavno za održavanje ako bismo ga nastavili proširivati ​​sa svim ostalim izrazima.

7. Zaključak

Obrazac dizajna tumača je sjajan za relativno jednostavno tumačenje gramatike, koji se ne treba puno razvijati i proširiti.

U gornjem primjeru pokazali smo da je moguće izgraditi SQL-sličan upit na objektno orijentirani način uz pomoć obrasca tumača.

Konačno, ovaj obrazac možete pronaći u JDK, posebno u java.util.Uzorak, java.text.Format ili java.text.Normalizer.

Kao i obično, cjeloviti kôd dostupan je na projektu Github.


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