본문 바로가기
까망 동네/디자인 패턴

[디자인패턴] 인터프리터 패턴 (Interpreter Pattern)

by 까망 하르방 2023. 11. 4.
반응형

🎈 인터프리터 패턴 (Interpreter Pattern) 

• 주로 문장 해석하는 목적으로 사용되는 패턴

• 정규 표현식, SQL 구문, 컴파일러 구현에 활용됨

• 디자인 패턴 중 행위 패턴에 해당된다.

 

💻 디자인 패턴(Design Pattern)이란?

👨‍💻 디자인 패턴(Design Pattern)이란? • SW 개발 방법 중에서도 구조적인 문제 해결에 목적을 둔다. • 알고리즘과 같이 특정 문제를 해결하는 Logic 형태보다는 특정 상황에 적용할 수 있는 방

zoosso.tistory.com

 

 

[예제 코드]

#include <iostream>

class Expression {
public:
    virtual int interpret() = 0;
};

class Number : public Expression {
private:
    int value;

public:
    Number(int val) : value(val) {}

    int interpret() override {
        return value;
    }
};

class Add : public Expression {
private:
    Expression* left;
    Expression* right;

public:
    Add(Expression* l, Expression* r) : left(l), right(r) {}

    int interpret() override {
        return left->interpret() + right->interpret();
    }
};

int main() {
    Expression* expression = new Add(new Number(5), new Number(3));

    int result = expression->interpret();

    std::cout << "5 + 3 = " << result << std::endl;

    return 0;
}

 

인터프리터 패턴 예시 결과

 

인터프리터 패턴 형태를 위한 쉬운 예제로

다양한 유형의 수식을 위한 클래스 확장을 쉽게 해볼 수 있다.

(단순 덧셈 연산에서 복잡하게 패턴을 적용해야 하나 싶을 것이다.)


 

[예제 코드] - 인터프리터 패턴 적용 X

#include <iostream>
#include <string>

bool lookupVariable(const std::string& var) {
    if (var == "A") return true;
    if (var == "B") return false;

    return false;
}

bool evaluateExpression(const std::string& expression) {
    bool ret = false;
    if (expression == "A AND B") {
        ret = lookupVariable("A") && lookupVariable("B");
        std::cout << "A AND B " << std::endl;
    }
    else if (expression == "NOT A") {
        ret = !lookupVariable("A");
        std::cout << "NOT A " << std::endl;
    }
    else if (expression == "A OR B AND A") {
        ret = lookupVariable("A") || lookupVariable("B") && lookupVariable("A");
        std::cout << "A OR B AND A " << std::endl;
    }
    else
    {
        std::cout << "등록되지 않은 수식!" << std::endl;
    }
    
    return ret;
}


int main() {
    std::string expression = "A AND B";

    bool result = evaluateExpression(expression);
    std::cout << "Result: " << (result ? "True" : "False") << std::endl;

    puts("\n");
    expression = "A OR B AND A";
    result = evaluateExpression(expression);
    std::cout << "Result: " << (result ? "True" : "False") << std::endl;


    return 0;
}

 

인터프리터 패턴 예시 결과

 

A, B는 변수로 예제에서는 단순하게 true/false로 바로 처리하였다.

→ 새로운 수식(espression)이 추가되면

  evaluateExpression 함수에서 if/else 코드 변경이 필요하다.

→ 표현식을 재귀적으로 처리하기도 복잡해 보인다.

 
[예제 코드] → 인터프리터 패턴 적용 O
#include <iostream>
#include <string>
#include <map>

class Context {
public:
    bool lookupVariable(const std::string& var) {
        if (var == "A") return true;
        if (var == "B") return false;
        // 다른 변수 처리 추가 가능
        return false;
    }
};

class Expression {
public:
    virtual bool interpret(Context& context) = 0;
};

class Variable : public Expression {
private:
    std::string name;

public:
    Variable(const std::string& varName) : name(varName) {}

    bool interpret(Context& context) override {
        return context.lookupVariable(name);
    }
};

class AndExpression : public Expression {
private:
    Expression* left;
    Expression* right;

public:
    AndExpression(Expression* l, Expression* r) : left(l), right(r) {}

    bool interpret(Context& context) override {
        std::cout << "AND 연산" << std::endl;
        return left->interpret(context) && right->interpret(context);
    }
};

class OrExpression : public Expression {
private:
    Expression* left;
    Expression* right;

public:
    OrExpression(Expression* l, Expression* r) : left(l), right(r) {}

    bool interpret(Context& context) override {
        std::cout << "OR 연산" << std::endl;
        return left->interpret(context) || right->interpret(context);
    }
};

class NotExpression : public Expression {
private:
    Expression* operand;

public:
    NotExpression(Expression* op) : operand(op) {}

    bool interpret(Context& context) override {
        std::cout << "NOT 연산" << std::endl;
        return !operand->interpret(context);
    }
};

int main() {
    // 논리식: (A && B) || !A
    Expression* expression =
        new OrExpression (
            new AndExpression(new Variable("A"), new Variable("B")), // A && B
            new NotExpression(new Variable("A") // !A
        )
    );

    Context context;
    bool result = expression->interpret(context);

    std::cout << "Result: " << (result ? "True" : "False") << std::endl;

    return 0;
}

 

인터프리터 패턴 예시 결과

 

논리 표현식을 추상 구문트리(Abstract Syntax Tree, AST)로 표현하고

이 트리를 해석하여 표현식을 계산할 수 있다.

이제는 새로운 연산자나 표현식에서 구조화된 방식으로 처리 가능하다.


 

🎈 인터프리터 패턴 장단점

+ 유연성: 새로운 문법/언어를 추가하거나 기존 문법 확장에 용이

+ 재사용성: 표현식 및 문법 구문의 구성 요소를 재사용 가능 (+ 다양한 조합)

 

-  성능저하: 실행 시간에 표현식을 해석하기 때문에 컴파일러와 달리

 성능면에서 부하가 발생할 수 있다.

 그래서 별도의 Parser 분석기를 이용하며 성능 저하를 줄여볼 수 있다.

-  복잡성: 여러 개의 표현식 or 복잡한 문법에서는 코드가 복잡해질 수 있다.

 

 

🎈 다른 디자인 패턴과 비교

• Composite

두 패턴은 계층 구조를 사용하며, 복합 객체를 다루는 것에서 유사하지만

인터프리터 패턴은 주로 언어 구문 해석 사용되는 것에 반해

컴포지트 패턴은 객체 구성을 트리 구조로 표현하고 복합 객체를 다루는 데 사용된다.

 

• 상태 (State)

두 패턴은 상태와 상태 전이를 관리에 비슷한 느낌이 있지만

인터프리터 패턴은 특정 도메인 언어 또는 표현식을 해석 등 사용 목적에 차이가 있다.

 

상태 패턴 (State Pattern)

상태(State) 패턴이란? • 객체 내부 상태에 맞춰 스스로 행동을 변경하는 패턴 • 객체는 마치 자신의 클래스를 바꾸는 것처럼 보인다. • if-else와 같은 분기문으로 상태전이 하는 것을 해소한다.

zoosso.tistory.com

 

 

• 전략 (Strategy)

두 패턴은 행동을 캡슐화하고 동적으로 변경 가능한 공통점이 있다.

 

💻 [디자인패턴] 전략 패턴 (Strategy Pattern)

전략 패턴 (Strategy Pattern) 이란? 전략(Strategy)은 코드 내부에서 로직(Logic)을 처리하는 「알고리즘」 어떤 목적 달성을 위한 수행 방식이라고 생각하면 좋다. 영화관에서 이벤트 영화 예매 방식을

zoosso.tistory.com

반응형

댓글