Replace Implicit Language with Interpreter (360)
02 Jul 2021 | DesignPattern Refactoring개요
한 클래스 내의 여러 메서드에서 일종의 묵시적 언어를 이루는 요소들을 조합하고 있다면,
그 묵시적 언어의 요소들을 각각의 클래스로 정의하고 그 객체의 조합을 통해 해석 가능한 수식을 만들어 낼 수 있도록 한다.
동기
Interpreter는 단순한 언어를 해석할 때 유용한 패턴이다. 문법을 Boolean과 클래스 몇 개로 모델화할 수 있을 때, 그 언어는 단순하다고 말한다. 단순한 언어의 문장이나 수식은 그 문법을 정의하는 클래스들의 인스턴스를 조합해 표현할 수 있다. 이때는 보통, Composite 패턴을 이용한다.
Interpreter 패턴으로 리팩터링할 때에는 초기 비용이 만만치 않다. 문법을 표현하는 클래스를 정의해야 하고, 클라이언트 코드에서도 그 문법 클래스의 조합을 통해 수식을 표현하도록 고쳐야 한다. 그런데도 리팩터링 할 가치가 있는 것을 알기 위해서는, 묵시적 언어에서는 끝없이 증가하는 수식의 조합을 처리하기 위해 수많은 중복 코드가 양산된다면 분명 가치가 있을 것이다.
Interpreter는 시스템 설정을 런타임에 변경하기 위해 사용되는 경우도 많다. 예를 들어, 시스템에서 사용자 인터페이스를 통해 사용자가 원하는 설정을 쿼리 형태로 입력받은 다음, 그 쿼리를 나타내는 해석 가능한 객체 구조를 동적으로 생성할 수 있다. 이런 식으로 인터프리터는 시스템 내의 모든 돚악이 정적이어서, 동적으로 설정할 수 없는 경우에는 불가능한 훨씬 더 큰 강력함과 융퉁성을 제공할 수 있다.
장점
- 언어를 묵시적으로 처리할 때보다 언어 요소를 쉽게 조합할 수 있다.
- 언어 요소의 새 조합을 지원하기 위해 코드를 추가 작성할 필요가 없다.
- 시스템의 동작 설정을 런타임에 변경할 수 있게 된다.
단점
- 문법을 정의하고 이를 이용하도록 클라이언트 코드를 수정하는 초기 비용이 든다.
- 언어가 복잡하면 지나치게 많은 작업이 필요하다.
- 언어가 단순하면, 설계만 복잡해지는 것이다.
절차
이 절차는 Specification과 Query Object 패턴에서 Interpreter패턴을 사용하는 방식과 관련이 많은데, 묵시적 언어는 많은 객체 선택 메서드를 사용하는 모델로 되어있고, 각각의 메서드는 주어진 조건을 만족하는 객체를 찾기 위해 컬렉션을 순회한다.
-
한 가지 조건을 입력받아 그에 맞는 객체의 집합을 검색하는 객체 선택 메서드를 찾고, 그 조건 파라미터에 대한 명세 클래스를 만든다. 이때 파라미터의 값은 생성자를 통해 받도록 하고
get
메서드도 제공해야 한다. 객체 선택 메서드 내에서는 명세 클래스 타입의 변수를 선언해 인스턴스를 만들고, 조건은 명세 객체로부터get
메서드를 통해 얻도록 수정한다.
명세 클래스의 이름은 그 역할이 잘 드러나도록 짓는다. 조건이 색상이었다면 ColorSpec 정도로 할 수 있다.
객체 선택 메서드에 조건이 두 개 이상 있다면, 각 조건에 대해 위의 과정과 단계 2의 과정을 반복한다. -
객체 선택 메서드 내의 조건문에 Extract Method를 적용해 bool값을 반환하는 메서드로 뽑아낸다. 그리고 뽑아낸 메서드를 명세 클래스로 옮긴다.
명세 클래스에 아직 수퍼클래스가 없다면 Extract Superclass 리팩터링을 통해 만들고, 새로 만든 수퍼클래스는 추상클래스로 정의한다. -
다른 객체 선택 메서드에 대해서 단계 1,2를 반복한다. 객체 선택 조건을 사용하는 다른 메서드에 대해서도 같은 작업을 한다.
-
명세를 두 개 이상 사용하는 객체 선택 메서드(즉, 객체 선택 로직에서 두 개 이상의 명세 클래스 인스턴스를 생성하는 메서드)가 있다면 조합 명세 클래스(객체 선택 메서드 안에서 명세 클래스의 인스턴스를 조합해 사용하느 클래스)를 만드는 식으로 단계 1을 약간 수정해 적용한다. 조합 명세 클래스의 생성자에 명세 객체를 넘길 수도 있고, 명세 클래스의 종류가 많은 경우에는 조합 명세 클래스에
add(...)
를 추가할 수도 있다.
그 다음 조합 명세 클래스도 명세 클래스들의 수퍼클래스를 상속하도록 수정한다. -
이제 모든 객체 선택 메서드가 한 명세 객체(즉, 하나의 명세 객체 또는 하나의 조합 명세 객체)를 사용한다. 또한 모든 객체 선택 메서드가 명세 객체를 생성하는 코드만 제외하면 완전히 동일할 것이다. 이 동일한 부분을 Extract Method를 통해 별도의 메서드로 뽑아낸다. 이 메서드의 이름은
selectBy(...)
와 같은 식으로 짓는다. 그리고 단계 2에서 만든 수퍼클래스 타입의 파라미터를 하나 받도록 하고, 객체의 컬렉션을 리턴하도록 한다. 예를 들면public List selectBy(Spec spec)
과 같이 정의할 수 있다.
이제 모든 객체 선택 메서드에서selectBy(...)
메서드를 사용하도록 수정한다. -
모든 객체 선택 메서드에 Inline Method를 적용한다.