
인터프리터 패턴은 반복적으로 등장하는 문제를 해결하기 위해 문제를 언어(문법)로 정의하고, 이를 기반으로 해석(interpret)하여 해결하는 패턴이다.
주로 간단한 문법이나 계산식을 처리하는 데 사용되며, 언어를 구성하는 요소를 클래스로 구현한다.

public class PostfixNotation {
private final String expression;
public PostfixNotation(String expression) {
this.expression = expression;
}
public static void main(String[] args) {
PostfixNotation postfixNotation = new PostfixNotation("123+-");
postfixNotation.calculate();
}
private void calculate() {
Stack<Integer> numbers = new Stack<>();
for (char c : this.expression.toCharArray()) {
switch (c) {
case '+':
numbers.push(numbers.pop() + numbers.pop());
break;
case '-':
int right = numbers.pop();
int left = numbers.pop();
numbers.push(left - right);
break;
default:
numbers.push(Integer.parseInt(c + ""));
}
}
System.out.println(numbers.pop());
}
}
PostfixNotation 클래스는 후위 표기법(Postfix Notation)으로 표현된 수식을 계산하는 클래스이다.123+-))calculate() 메서드:private void calculate() {
Stack<Integer> numbers = new Stack<>();
for (char c : this.expression.toCharArray()) {
switch (c) {
case '+':
numbers.push(numbers.pop() + numbers.pop());
break;
case '-':
int right = numbers.pop();
int left = numbers.pop();
numbers.push(left - right);
break;
default:
numbers.push(Integer.parseInt(c + ""));
}
}
System.out.println(numbers.pop());
}
문제점
calculate() 메서드를 수정해야 함.변경 내용
표현(Expression)을 객체화:
PostfixExpression 인터페이스를 구현.확장성 증가:
Parser 도입:
PostfixParser 추가.구조
PostfixExpression 인터페이스:interpret(Map<Character, Integer> context) 메서드로 계산 수행.public interface PostfixExpression {
int interpret(Map<Character, Integer> context);
}
숫자(Variable) 표현
public class VariableExpression implements PostfixExpression {
private Character character;
public VariableExpression(Character character) {
this.character = character;
}
@Override
public int interpret(Map<Character, Integer> context) {
return context.get(this.character);
}
}
덧셈(Plus) 표현
public class PlusExpression implements PostfixExpression {
private PostfixExpression left, right;
public PlusExpression(PostfixExpression left, PostfixExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Map<Character, Integer> context) {
return left.interpret(context) + right.interpret(context);
}
}
뺄셈(Minus) 표현
public class MinusExpression implements PostfixExpression {
private PostfixExpression left, right;
public MinusExpression(PostfixExpression left, PostfixExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Map<Character, Integer> context) {
return left.interpret(context) - right.interpret(context);
}
}
PostfixParser:public class PostfixParser {
public static PostfixExpression parse(String expression) {
Stack<PostfixExpression> stack = new Stack<>();
for (char c : expression.toCharArray()) {
stack.push(getExpression(c, stack));
}
return stack.pop();
}
private static PostfixExpression getExpression(char c, Stack<PostfixExpression> stack) {
switch (c) {
case '+':
return new PlusExpression(stack.pop(), stack.pop());
case '-':
PostfixExpression right = stack.pop();
PostfixExpression left = stack.pop();
return new MinusExpression(left, right);
default:
return new VariableExpression(c);
}
}
}
PostfixParser를 사용해 수식을 해석하고 결과를 계산.public class App {
public static void main(String[] args) {
PostfixExpression expression = PostfixParser.parse("xyz+-a+");
int result = expression.interpret(Map.of('x', 1, 'y', 2, 'z', 3, 'a', 4));
System.out.println(result); // 출력: 0
}
}
코드 흐름
1. 문자열 분석:
2. 객체 트리 생성:
3. 해석 및 계산:
4. 결과 출력:
다이어그램

After 리팩토링
public interface PostfixExpression {
int interpret(Map<Character, Integer> context);
static PostfixExpression plus(PostfixExpression left, PostfixExpression right) {
return context -> left.interpret(context) + right.interpret(context);
}
static PostfixExpression minus(PostfixExpression left, PostfixExpression right) {
return context -> left.interpret(context) - right.interpret(context);
}
static PostfixExpression multiply(PostfixExpression left, PostfixExpression right) {
return context -> left.interpret(context) * right.interpret(context);
}
static PostfixExpression variable(Character c) {
return context -> context.get(c);
}
}
이런식으로 별도의 클래스를 만들지 않고 parse와 expression에서 다 처리할 수 있다. (다만 조금 더 이해하기 복잡할 수 있음.)
1. 장점
Expression 클래스를 구현하면 됨.2. 단점
Expression 클래스와 Parser가 많아져 코드의 복잡도가 증가.1. 자바 컴파일러
자바 컴파일러는 소스 코드를 바이트코드로 변환하며, 내부적으로 인터프리터 패턴의 구조를 활용하여 문법을 해석하고 처리한다.
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(App.class);
app.setWebApplicationType(WebApplicationType.NONE); // CLI 애플리케이션 설정
app.run(args);
}
}
2. 정규 표현식
정규 표현식(Regex)은 문자열 패턴을 정의하고, 이를 기반으로 문자열을 해석하는 데 사용된다.
자바의 java.util.regex 패키지는 인터프리터 패턴을 활용한다.
public class InterpreterInJava {
public static void main(String[] args) {
System.out.println(Pattern.matches(".pr...", "spring")); // true
System.out.println(Pattern.matches("[a-z]{6}", "spring")); // true
System.out.println(Pattern.matches("white[a-z]{4}[0-9]{4}", "whiteship2000")); // true
System.out.println(Pattern.matches("\\d", "1")); // true (숫자 1개 매칭)
System.out.println(Pattern.matches("\\D", "a")); // true (숫자가 아닌 문자 매칭)
}
}
Pattern.matches(): 문자열이 정규 표현식에 매칭되는지 확인..pr...: "pr"을 포함하는 6자리 문자열.[a-z]{6}: 소문자 6자리 문자열.white[a-z]{4}[0-9]{4}: "white"로 시작하고, 뒤에 소문자 4자리와 숫자 4자리가 이어지는 패턴.\\d: 숫자 1개.\\D: 숫자가 아닌 문자.3. 스프링 Expression Language (SpEL)
SpEL은 스프링에서 제공하는 표현 언어로, 동적으로 값을 참조하거나 계산식을 평가할 수 있다.
public class InterpreterInSpring {
public static void main(String[] args) {
Book book = new Book("spring");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("title"); // 'title' 필드 참조
System.out.println(expression.getValue(book)); // 출력: spring
}
}
public class Book {
private String title;
public Book(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
SpelExpressionParser는 title이라는 속성을 해석.@Value 애노테이션 사용@Service
public class MyService implements ApplicationRunner {
@Value("#{2 + 5}") // SpEL 표현식으로 값 계산
private String value;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(value); // 출력: 7
}
}
@Value: SpEL을 활용해 속성을 동적으로 계산.2 + 5라는 표현식을 해석하여 결과 값을 필드에 주입.
public class Board {
List<Post> posts = new ArrayList<>();
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
public void addPost(String content) {
this.posts.add(new Post(content));
}
}
public class Post {
private String title;
private LocalDateTime createdDateTime;
public Post(String title) {
this.title = title;
this.createdDateTime = LocalDateTime.now();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public LocalDateTime getCreatedDateTime() {
return createdDateTime;
}
public void setCreatedDateTime(LocalDateTime createdDateTime) {
this.createdDateTime = createdDateTime;
}
}
public class Client {
public static void main(String[] args) {
Board board = new Board();
board.addPost("디자인 패턴 게임");
board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");
board.addPost("지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다.");
// TODO 들어간 순서대로 순회하기
List<Post> posts = board.getPosts();
for (int i = 0 ; i < posts.size() ; i++) {
Post post = posts.get(i);
System.out.println(post.getTitle());
}
// TODO 가장 최신 글 먼저 순회하기
Collections.sort(posts, (p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime()));
for (int i = 0 ; i < posts.size() ; i++) {
Post post = posts.get(i);
System.out.println(post.getTitle());
}
}
}
구조 및 문제점
Board 클래스:posts) 제공.addPost()로 게시글 추가.for 루프 사용.Collections.sort() 호출.for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i);
System.out.println(post.getTitle());
}
Collections.sort(posts, (p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime()));
for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i);
System.out.println(post.getTitle());
}
Board 클래스 모두 수정해야 한다.
Iterator인터페이스 활용
RecentPostIterator)로 분리.Iterator 인터페이스를 통해 순회.
Board클래스 변경
public class Board {
List<Post> posts = new ArrayList<>();
public List<Post> getPosts() {
return posts;
}
public void addPost(String content) {
this.posts.add(new Post(content));
}
public Iterator<Post> getRecentPostIterator() {
return new RecentPostIterator(this.posts);
}
}
getRecentPostIterator() 추가로 최신 글 순회를 위한 이터레이터 반환.Collections.sort()를 호출하지 않고도 최신 글 순회를 수행.Iterator<Post> recentPostIterator = board.getRecentPostIterator();
while (recentPostIterator.hasNext()) {
System.out.println(recentPostIterator.next().getTitle());
}
RecentPostIterator
public class RecentPostIterator implements Iterator<Post> {
private Iterator<Post> internalIterator;
public RecentPostIterator(List<Post> posts) {
Collections.sort(posts, (p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime()));
this.internalIterator = posts.iterator();
}
@Override
public boolean hasNext() {
return this.internalIterator.hasNext();
}
@Override
public Post next() {
return this.internalIterator.next();
}
}
Iterator 인터페이스를 사용하여 일관된 방식으로 순회.다이어그램

대략적인 로직을 살펴보자면,
1. 게시글 추가:
2. 순회 이터레이터 생성:
3.게시글 순회:
장점
내부 구조 캡슐화:
유연한 순회 방법 제공:
일관된 인터페이스:
Iterator 인터페이스를 사용하여 다양한 집합 구조를 동일하게 처리.단점
클래스 증가:
복잡도 증가:
java.util.IteratorList, Set, Map)에서 제공.List<String> list = Arrays.asList("A", "B", "C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
java.util.Enumeration
Java StAX:
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader reader = factory.createXMLEventReader(new FileInputStream("Book.xml"));
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement() && "book".equals(event.asStartElement().getName().getLocalPart())) {
System.out.println(event.asStartElement().getAttributeByName(new QName("title")).getValue());
}
}
CompositeIterator중재자 패턴은 여러 객체가 직접적으로 소통하지 않고, 중재자(Mediator)를 통해 간접적으로 소통하게 만드는 패턴이다.

Colleague는 구체적인 클래스에 의존하지 않고 인터페이스인 Mediator만 알면 된다!
이룰 통해
1) 객체 간의 복잡한 의존성을 제거하고 결합도를 낮추고,
2) 중앙 중재자(Mediator) 클래스가 커뮤니케이션을 관리하도록 한다!
- 클래스 간 강한 결합
Guest는 Restaurant와 CleaningService를 직접 참조.Restaurant는 CleaningService를 직접 참조.
- 확장성 부족
Before 코드
public class Hotel {
public static void main(String[] args) {
Guest guest = new Guest();
guest.getTower(3); // Guest가 직접 CleaningService 호출
guest.dinner(); // Guest가 직접 Restaurant 호출
Restaurant restaurant = new Restaurant();
restaurant.clean(); // Restaurant이 CleaningService 호출
}
}
Restaurant와 CleaningService를 직접 호출.
public class Guest {
private Restaurant restaurant = new Restaurant();
private CleaningService cleaningService = new CleaningService();
public void dinner() {
restaurant.dinner(this);
}
public void getTower(int numberOfTower) {
cleaningService.getTower(this, numberOfTower);
}
}
CleaningService를 직접 호출.
public class Restaurant {
private CleaningService cleaningService = new CleaningService();
public void dinner(Guest guest) {
System.out.println("dinner " + guest);
}
public void clean() {
cleaningService.clean(this);
}
}
Guest와 Restaurant를 직접 처리.
public class CleaningService {
public void clean(Gym gym) {
System.out.println("clean " + gym);
}
public void getTower(Guest guest, int numberOfTower) {
System.out.println(numberOfTower + " towers to " + guest);
}
public void clean(Restaurant restaurant) {
System.out.println("clean " + restaurant);
}
}
개선 사항
FrontDesk 중재자 도입:
FrontDesk를 통해 이루어짐.Guest, Restaurant, CleaningService 간의 결합도 제거.클래스 역할 분리:
Guest:FrontDesk에 전달.FrontDesk:CleaningService, Restaurant)로 라우팅.CleaningService:After 코드 로직
1. FrontDesk 중재자
public class FrontDesk {
private CleaningService cleaningService = new CleaningService();
private Restaurant restaurant = new Restaurant();
public void getTowers(Guest guest, int numberOfTowers) {
cleaningService.getTowers(guest.getId(), numberOfTowers);
}
public String getRoomNumberFor(Integer guestId) {
return "1111";
}
public void dinner(Guest guest, LocalDateTime dateTime) {
restaurant.dinner(guest.getId(), dateTime);
}
}
2. Guest 클래스
public class Guest {
private Integer id;
private FrontDesk frontDesk = new FrontDesk();
public void getTowers(int numberOfTowers) {
this.frontDesk.getTowers(this, numberOfTowers);
}
private void dinner(LocalDateTime dateTime) {
this.frontDesk.dinner(this, dateTime);
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
3. CleaningService 클래스
public class CleaningService {
private FrontDesk frontDesk = new FrontDesk();
public void getTowers(Integer guestId, int numberOfTowers) {
String roomNumber = this.frontDesk.getRoomNumberFor(guestId);
System.out.println("provide " + numberOfTowers + " towers to " + roomNumber);
}
}
4. Restaurant 클래스
public class Restaurant {
public void dinner(Integer id, LocalDateTime dateTime) {
System.out.println("Dinner reserved for guest " + id + " at " + dateTime);
}
}

추가적으로 현재 예제 같은 경우, 여러 중재자가 아니고, 중재자를 교체할 필요가 없기 때문에 인터페이스가 아닌 단일 클래스로 설계하였다. 만약 다양한 중재자가 필요하거나, 확장성이 요구될 때 인터페이스를 도입할 필요가 있을 것 같다!
장점
컴포넌트 코드 변경 없이 새로운 중재자 생성 가능
컴포넌트 코드 간결화
단점
중재자 클래스의 복잡도 증가
중재자에 의존성 집중
1. 자바에서의 활용
ExecutorService, Executor:2. 스프링에서의 활용
DispatcherServlet:public class MediatorInSpring {
public static void main(String[] args) {
DispatcherServlet dispatcherServlet;
}
}
DispatcherServlet은 중재자 역할을 수행하며, HTTP 요청을 수신하고 적절한 컨트롤러에 전달.