1. 개요
이 사용방법(예제)에서는 동작 GoF 디자인 패턴 중 하나인 인터프리터를 소개합니다.
처음에는 목적에 대한 개요를 제공하고 해결하려는 문제를 설명합니다.
그런 다음 Interpreter의 UML 다이어그램과 실제 예제 구현을 살펴보겠습니다.
2. 통역사 디자인 패턴
즉, 패턴 은 인터프리터 자체에서 평가할 수 있는 객체 지향 방식으로 특정 언어의 문법을 정의합니다 .
이를 염두에 두고 기술적으로 사용자 지정 정규식, 사용자 지정 DSL 인터프리터를 구축하거나 인간 언어를 구문 분석하고 추상 구문 트리를 구축한 다음 해석을 실행할 수 있습니다.
이것들은 잠재적인 사용 사례의 일부일 뿐이지만 잠시 생각하면 IDE에서와 같이 더 많은 사용 사례를 찾을 수 있습니다. IDE는 우리가 작성하는 코드를 지속적으로 해석하여 귀중한 힌트.
인터프리터 패턴은 일반적으로 문법이 비교적 단순할 때 사용해야 합니다.
그렇지 않으면 유지 관리가 어려워질 수 있습니다.
3. UML 다이어그램
위의 다이어그램은 컨텍스트 와 표현식 이라는 두 가지 주요 엔터티를 보여줍니다 .
이제 모든 언어는 어떤 방식으로든 표현되어야 하며 단어(표현)는 주어진 맥락에 따라 어떤 의미를 갖게 됩니다.
AbstractExpression 은 컨텍스트 를 매개 변수로 사용하는 하나의 추상 메서드를 정의합니다 . 덕분에 각 표현식은 컨텍스트 에 영향을 미치고 상태를 변경하며 해석을 계속하거나 결과 자체를 반환합니다.
따라서 컨텍스트는 전역 처리 상태의 소유자가 될 것이며 전체 해석 프로세스 중에 재사용될 것입니다.
TerminalExpression 과 NonTerminalExpression 의 차이점은 무엇 입니까?
NonTerminalExpression 에는 하나 이상의 다른 AbstractExpression 이 연관되어 있을 수 있으므로 재귀적으로 해석될 수 있습니다. 결국 해석 과정은 결과를 반환할 TerminalExpression 으로 마무리되어야 합니다.
NonTerminalExpression 이 복합 이라는 점에 유의할 가치가 있습니다.
마지막으로 클라이언트의 역할은 이미 생성된 추상 구문 트리 를 생성하거나 사용하는 것인데, 이는 생성된 언어로 정의된 문장에 지나지 않습니다 .
4. 시행
작동 중인 패턴을 보여주기 위해 객체 지향 방식으로 간단한 SQL과 유사한 구문을 빌드한 다음 해석하여 결과를 반환합니다.
먼저 Select, From 및 Where 표현식을 정의하고 클라이언트 클래스에서 구문 트리를 빌드하고 해석을 실행합니다.
Expression 인터페이스에는 다음 과 같은 해석 방법이 있습니다.
List<String> interpret(Context ctx);
다음으로 첫 번째 표현식인 Select 클래스를 정의합니다.
class Select implements Expression {
private String column;
private From from;
// constructor
@Override
public List<String> interpret(Context ctx) {
ctx.setColumn(column);
return from.interpret(ctx);
}
}
선택될 열 이름과 From 유형의 또 다른 구체적인 표현식 을 생성자의 매개변수로 가져옵니다.
재정의된 interpret() 메서드에서는 컨텍스트의 상태를 설정하고 해석을 컨텍스트와 함께 다른 식으로 전달합니다.
그렇게 하면 NonTerminalExpression임을 알 수 있습니다.
또 다른 표현은 From 클래스입니다.
class From implements Expression {
private String table;
private Where where;
// constructors
@Override
public List<String> interpret(Context ctx) {
ctx.setTable(table);
if (where == null) {
return ctx.search();
}
return where.interpret(ctx);
}
}
이제 SQL에서 where 절은 선택적이므로 이 클래스는 터미널이거나 비터미널 표현식입니다.
사용자가 where 절을 사용하지 않기로 결정하면 From 표현식은 ctx.search() 호출로 종료되고 결과를 반환합니다. 그렇지 않으면 더 해석될 것입니다.
Where 표현식 은 필요한 필터를 설정하여 컨텍스트를 다시 수정하고 검색 호출로 해석을 종료합니다.
class Where implements Expression {
private Predicate<String> filter;
// constructor
@Override
public List<String> interpret(Context ctx) {
ctx.setFilter(filter);
return ctx.search();
}
}
예를 들어 Context 클래스는 데이터베이스 테이블을 모방하는 데이터를 보유합니다.
Expression 의 각 하위 클래스 와 검색 방법 에 의해 수정되는 세 개의 키 필드가 있습니다 .
class Context {
private static Map<String, List<Row>> tables = new HashMap<>();
static {
List<Row> 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<String> whereFilter;
// ...
List<String> search() {
List<String> 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;
}
}
검색이 완료되면 컨텍스트가 자체적으로 지워지므로 열, 테이블 및 필터가 기본값으로 설정됩니다.
그렇게 하면 각 해석이 다른 해석에 영향을 미치지 않습니다.
5. 테스트
테스트를 위해 InterpreterDemo 클래스를 살펴보겠습니다.
public class InterpreterDemo {
public static void main(String[] args) {
Expression query = new Select("name", new From("people"));
Context ctx = new Context();
List<String> result = query.interpret(ctx);
System.out.println(result);
Expression query2 = new Select("*", new From("people"));
List<String> result2 = query2.interpret(ctx);
System.out.println(result2);
Expression query3 = new Select("name",
new From("people",
new Where(name -> name.toLowerCase().startsWith("d"))));
List<String> result3 = query3.interpret(ctx);
System.out.println(result3);
}
}
먼저 생성된 표현식으로 구문 트리를 구축하고 컨텍스트를 초기화한 다음 해석을 실행합니다. 컨텍스트는 재사용되지만 위에서 본 것처럼 각 검색 호출 후에 자체적으로 정리됩니다.
프로그램을 실행하면 출력은 다음과 같아야 합니다.
[John, Jan, Dominic]
[John Doe, Jan Kowalski, Dominic Doom]
[Dominic]
6. 단점
문법이 복잡해지면 유지하기가 더 어려워집니다.
제시된 예에서 볼 수 있습니다. Limit 와 같은 다른 표현식을 추가하는 것은 합리적으로 쉬울 것 입니다. 그러나 다른 모든 표현식으로 계속 확장하기로 결정한다면 유지하기가 너무 쉽지 않을 것입니다.
7. 결론
인터프리터 디자인 패턴은 상대적으로 간단한 문법 해석 에 적합하며 많은 발전과 확장이 필요하지 않습니다.
위의 예에서 우리는 인터프리터 패턴의 도움으로 객체 지향 방식으로 SQL과 유사한 쿼리를 작성할 수 있음을 보여주었습니다.
마지막으로 JDK, 특히 java.util.Pattern , java.text.Format 또는 java.text.Normalizer 에서 이 패턴 사용을 찾을 수 있습니다 .
평소와 같이 전체 코드는 Github 프로젝트 에서 사용할 수 있습니다 .