자바의 인터프리터 디자인 패턴

1. 개요

이 튜토리얼에서는 행동 GoF 디자인 패턴 중 하나 인 인터프리터를 소개합니다.

처음에는 목적에 대한 개요를 제공하고 해결하려는 문제를 설명합니다.

그런 다음 Interpreter의 UML 다이어그램과 실제 예제 구현을 살펴 보겠습니다.

2. 통역사 디자인 패턴

간단히 말해서, 패턴 인터프리터 자체가 평가할 수있는 객체 지향 방식으로 특정 언어의 문법을 정의합니다 .

이를 염두에두고 기술적으로 사용자 지정 정규 표현식, 사용자 지정 DSL 인터프리터를 구축하거나 인간 언어를 구문 분석 하고 추상 구문 트리를 구축 한 다음 해석을 실행할 수 있습니다.

이는 잠재적 인 사용 사례 중 일부에 불과하지만 잠시 생각하면 IDE에서 더 많은 사용 사례를 찾을 수 있습니다. 예를 들어 IDE는 우리가 작성하는 코드를 지속적으로 해석하여 우리에게 제공하기 때문입니다. 귀중한 힌트.

통역사 패턴은 일반적으로 문법이 비교적 간단한 경우에 사용해야합니다.

그렇지 않으면 유지 관리가 어려워 질 수 있습니다.

3. UML 다이어그램

위의 다이어그램은 ContextExpression 의 두 가지 주요 엔티티를 보여줍니다 .

이제 모든 언어는 어떤 식 으로든 표현되어야하며, 단어 (표현식)는 주어진 문맥에 따라 어떤 의미를 갖게 될 것입니다.

AbstractExpression 은 컨텍스트를 취하는 하나의 추상 메서드를 정의합니다.매개 변수로. 덕분에 각 표현식은 컨텍스트에 영향을 미치고 상태를 변경하며 해석을 계속하거나 결과 자체를 반환합니다.

따라서 컨텍스트는 처리의 글로벌 상태의 보유자가 될 것이며 전체 해석 프로세스 동안 재사용 될 것입니다.

그렇다면 TerminalExpressionNonTerminalExpression 의 차이점은 무엇 입니까?

NonTerminalExpression는 하나 이상의 다른있을 수 있습니다 AbstractExpressions 따라서 재귀 적으로 해석 할 수있다, 거기에 관련합니다. 결국 해석 과정은 결과를 반환 하는 TerminalExpression으로 끝나야 합니다.

NonTerminalExpression복합 이라는 점에 유의할 필요있습니다.

마지막으로 클라이언트의 역할은 생성 된 언어로 정의 된 문장에 불과한 이미 생성 된 추상 구문 트리 를 생성하거나 사용하는 것 입니다.

4. 구현

실제 패턴을 보여주기 위해 우리는 객체 지향 방식으로 간단한 SQL과 같은 구문을 구축 할 것입니다. 그런 다음 해석되어 결과를 반환합니다.

먼저 Select, FromWhere 표현식을 정의 하고 클라이언트 클래스에 구문 트리를 만들고 해석을 실행합니다.

인터페이스는이 법을 해석해야합니다 :

List interpret(Context ctx);

다음으로 첫 번째 표현식 인 Select 클래스를 정의합니다 .

class Select implements Expression { private String column; private From from; // constructor @Override public List interpret(Context ctx) { ctx.setColumn(column); return from.interpret(ctx); } }

선택할 열 이름과 From 유형의 또 다른 구체적인 Expression 을 생성자의 매개 변수로 가져옵니다.

재정의 된 translate () 메서드에서 컨텍스트의 상태를 설정하고 컨텍스트와 함께 다른 식으로 해석을 전달합니다.

이렇게하면 NonTerminalExpression임을 알 수 있습니다.

또 다른 표현식은 From 클래스입니다.

class From implements Expression { private String table; private Where where; // constructors @Override public List interpret(Context ctx) { ctx.setTable(table); if (where == null) { return ctx.search(); } return where.interpret(ctx); } }

이제 SQL에서 where 절은 선택 사항이므로이 클래스는 터미널 또는 비 터미널 표현식입니다.

If the user decides not to use a where clause, the From expression it's going to be terminated with the ctx.search() call and return the result. Otherwise, it's going to be further interpreted.

The Where expression is again modifying the context by setting the necessary filter and terminates the interpretation with search call:

class Where implements Expression { private Predicate filter; // constructor @Override public List interpret(Context ctx) { ctx.setFilter(filter); return ctx.search(); } }

For the example, the Context class holds the data which is imitating the database table.

Note that it has three key fields which are modified by each subclass of Expression and the search method:

class Context { private static Map
    
      tables = new HashMap(); static { List list = new ArrayList(); list.add(new Row("John", "Doe")); list.add(new Row("Jan", "Kowalski")); list.add(new Row("Dominic", "Doom")); tables.put("people", list); } private String table; private String column; 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()); clear(); return result; } }
    

After the search is done, the context is clearing itself, so the column, table, and filter are set to defaults.

That way each interpretation won't affect the other.

5. Testing

For testing purposes, let’s have a look at the InterpreterDemo class:

public class InterpreterDemo { public static void main(String[] args) { Expression query = new Select("name", new From("people")); Context ctx = new Context(); List result = query.interpret(ctx); System.out.println(result); Expression query2 = new Select("*", new From("people")); List result2 = query2.interpret(ctx); System.out.println(result2); Expression query3 = new Select("name", new From("people", new Where(name -> name.toLowerCase().startsWith("d")))); List result3 = query3.interpret(ctx); System.out.println(result3); } }

First, we build a syntax tree with created expressions, initialize the context and then run the interpretation. The context is reused, but as we showed above, it cleans itself after each search call.

By running the program, the output should be as follow:

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

6. Downsides

When the grammar is getting more complex, it becomes harder to maintain.

It can be seen in the presented example. It'd be reasonably easy to add another expression, like Limit, yet it won't be too easy to maintain if we'd decide to keep extending it with all other expressions.

7. Conclusion

인터프리터 디자인 패턴은 많은 발전과 확장이 필요없는 비교적 단순한 문법 해석 에 적합합니다.

위의 예에서 인터프리터 패턴을 사용하여 객체 지향 방식으로 SQL과 유사한 쿼리를 작성할 수 있음을 보여주었습니다.

마지막으로 JDK, 특히 java.util.Pattern , java.text.Format 또는 java.text.Normalizer 에서이 패턴 사용법을 찾을 수 있습니다 .

평소처럼 전체 코드는 Github 프로젝트에서 사용할 수 있습니다.