1. 개요

이 사용방법(예제)에서는 동작 GoF 디자인 패턴 중 하나인 인터프리터를 소개합니다.

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

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

2. 통역사 디자인 패턴

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

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

이것들은 잠재적인 사용 사례의 일부일 뿐이지만 잠시 생각하면 IDE에서와 같이 더 많은 사용 사례를 찾을 수 있습니다. IDE는 우리가 작성하는 코드를 지속적으로 해석하여 귀중한 힌트.

인터프리터 패턴은 일반적으로 문법이 비교적 단순할 때 사용해야 합니다.

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

3. UML 다이어그램

통역사

위의 다이어그램은 컨텍스트표현식 이라는 두 가지 주요 엔터티를 보여줍니다 .

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

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

따라서 컨텍스트는 전역 처리 상태의 소유자가 될 것이며 전체 해석 프로세스 중에 재사용될 것입니다.

TerminalExpression 과  NonTerminalExpression 의 차이점은 무엇  입니까?

NonTerminalExpression 에는  하나 이상의 다른 AbstractExpression 이 연관되어 있을 수 있으므로 재귀적으로 해석될 수 있습니다. 결국 해석 과정은   결과를 반환할 TerminalExpression 으로 마무리되어야 합니다.

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

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

4. 시행

작동 중인 패턴을 보여주기 위해 객체 지향 방식으로 간단한 SQL과 유사한 구문을 빌드한 다음 해석하여 결과를 반환합니다.

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

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.Patternjava.text.Format 또는  java.text.Normalizer 에서 이 패턴 사용을 찾을 수 있습니다  .

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

Generic footer banner