반응형
Interpreter Pattern
- 해석자 (interpreter) 패턴이란?
- 언어(language) 의 문법 규칙을 표현하고 해당 언어의 해석을 하는데 사용되는 행동 패턴
- 컴파일러 또는 인터프리터를 개발할 때 적용되며 언어의 문법 구조를 나타내는데 유용
- 문법 규칙을 클래스로 표현하고, 이러한 클래스들을 조합하여 언어의 문장을 해석하는 구조를 만듬
- 새로운 언어를 추가하거나 기존 언어의 문법을 변경할 때 유연하게 확장 가능!
- 언어(language) 의 문법 규칙을 표현하고 해당 언어의 해석을 하는데 사용되는 행동 패턴
- 해석자 패턴을 도입하기 위한 예제 - Postfix expression
- Infix notation
- 일반적인 표기법, 사람들이 보기에 익수함
- 연산자 우선순위 때문에 괄호가 필요함 (우선순위 처리 복잡)
- Postfix notation
- 사람이 보기에 직관적이지 않지만, 수식을 읽으면서 바로 계산이 가능
- 수신에 괄호가 필요 없음
- 수식 자체에 연산자의 우선순위가 포함되어 표현됨
- 사람이 보기에 직관적이지 않지만, 수식을 읽으면서 바로 계산이 가능
- 특정 연산자는 가장 마지막으로 등장한 두 피연산자에 대해 계산
- 가장 마지막으로 등장한 피연산자 (결과값)을 파악하기 위해 스택을 활용!
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(); } public void calculate() { Stack<Integer> numbers = new Stack<>(); for (char c : 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()); // 최종 결과 출력 } }
- Infix notation
- 해석자 패턴의 구조
- 특정 행동들을 핸들러라는 독립 실행형 객체로 변환
- 책임자 패턴 예시 코드 (Terminal expression)
public class App { public static void main(String[] args) { PostfixExpression expression = PostfixParser.parse("xyz+-a"); Map<Character, Integer> context = new HashMap<>(); context.put('x', 2); context.put('y', 3); context.put('z', 1); context.put('a', 4); int result = expression.interpret(context); // 표현식 해석 System.out.println(result); // 결과 출력 } } public interface PostfixExpression { int interpret(Map<Character, Integer> context); } 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); // 변수의 값 반환 } }
- 책임자 패턴 예시 코드 (Nonterminal expression)
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); // 좌 + 우 } } 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); // 좌 - 우 } } 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); // 변수 처리 } } }
- 장점
- 새로운 언어 요소나 문법 규칙을 추가하기가 유연함
- 단순한 문법을 가지는 언어의 경우 인터프리터 패턴이 효과적
- 언어의 문법이나 규칙이 변경되더라도 인터프리터 패턴은 변화에 대응이 쉬움
- 단점
- 언어의 문법이 복잡한 경우 인터프리터 패턴의 클래스 계층 구조가 복잡해짐
- 일부 복잡한 언어나 대규모 문장에 대해서는 패턴 적용 시 성능이 저하될 수 있음
Iterator Pattern
- 반복자 (iterator) 패턴이란?
- 컬렉션(collection) 요소들의 기본 표현 (리스트, 스택, 트리 등)을 노출하지 않고 컬렉션 내 요소를 하나씩 순회할 수 있도록 하는 행동 패턴
- 반복자 패턴이 필요한 상황
- 데이터 컬렉션이란 객체들을 그룹으로 묶어 자료구조에 맞게 저장함
- 선형적인 자료구조 (배열, 리스트 등) 는 순차적으로 요소 조회를 할 수 있지만, 트리, 해시와 같은 비선형적인 자료구조는 순회의 기준이 필요함
- 클라이언트가 컬렉션에 종류를 따지지 않고 순회할 수 있는 방법이 있을까?
- 반복자 패턴의 아이디어
- 컬렉션의 순회 동작을 iterator 라는 별도 객체로 추출!
- 반복자 패턴의 구조
- 반복자 패턴 코드 예시
public interface IterableCollection { Iterator iterator(); // Iterator 반환 } public class ArrayCollection implements IterableCollection { Object[] array; int index = 0; // 현재 요소 추가 위치 public ArrayCollection(int size) { this.array = new Object[size]; // 지정된 크기의 배열 생성 } public void append(Object obj) { if (index < array.length) { array[index++] = obj; // 배열에 요소 추가 } } @Override public Iterator iterator() { return new ConcreteIterator(array); // ConcreteIterator 생성 } } public interface Iterator { boolean hasNext(); // 다음 요소가 있는지 확인 Object next(); // 다음 요소 반환 } public class ConcreteIterator implements Iterator { Object[] array; private int nextIndex = 0; // 현재 순회 위치 public ConcreteIterator(Object[] array) { this.array = array; } @Override public boolean hasNext() { return nextIndex < array.length; // 더 순회할 요소가 있는지 확인 } @Override public Object next() { return array[nextIndex++]; // 다음 요소 반환 후 인덱스 증가 } } public class Client { public static void main(String[] args) { ArrayCollection array = new ArrayCollection(5); // 크기가 5인 컬렉션 생성 array.append(1); array.append(2); array.append(3); array.append(4); array.append(5); Iterator it = array.iterator(); // Iterator 생성 while (it.hasNext()) { // 순회 System.out.printf("%s -> ", it.next()); // 요소 출력 } } }
- 장점
- 일관된 이터레이터 인터페이스를 사용해 여러 종류의 컬렉션에 대해 동일한 순회 인터페이스 제공
- 클라이언트는 컬렉션 내부 구조 및 순회 방식을 알지 않아도 됨
- 컬렉션의 구현과 접근하는 부분을 반복자로 분리해 결합도를 낮춤
- 단점
- 만일 앱이 간단한 컬렉션에서만 작동하는 경우 패턴 적용시 코드가 복잡해짐
- 이터레이터 객체를 만드는 것이 필요한 상황인지 판단할 필요가 있음
- JAVA 에서 반복자 패턴이 적용된 사례
- java.util.Iterator
- hasNext() : 순회할 요소가 있는지 확인
- next() : 현재 커서의 요소를 출력하고 다음 위치로 커서를 이동
- remove() : iterator 에서 next() 로 받았던 해당 요소를 삭제
- 지원되는 경우만 가능 (보통 동시 다발적으로 같은 명령을 수행해도 안전한 컬렉션에서 제공)
import java.util.*; public class Main { public static void main(String[] args) { // TreeSet으로 정렬된 컬렉션 생성 Set<Integer> aggregate = new TreeSet<>(List.of(1, 2, 3, 4, 5)); // Iterator 생성 Iterator<Integer> itr = aggregate.iterator(); // 컬렉션 순회 while (itr.hasNext()) { System.out.printf("%d 삭제 ", itr.next()); itr.remove(); // 요소 삭제 } // 컬렉션 상태 출력 System.out.println(aggregate); // 출력: [] } }
- 지원되는 경우만 가능 (보통 동시 다발적으로 같은 명령을 수행해도 안전한 컬렉션에서 제공)
- forEachRemaining() : 함수형 인터페이스를 통해 순회 코드를 단순하게 만듦
import java.util.*; public class Main { public static void main(String[] args) { // TreeSet으로 정렬된 컬렉션 생성 Set<Integer> aggregate = new TreeSet<>(List.of(1, 2, 3, 4, 5)); // forEachRemaining() 사용 aggregate.iterator().forEachRemaining(System.out::println); } } public class Main { public static void main(String[] args) { // HashSet 생성 Set<String> set = new HashSet<>(); set.add("apple"); set.add("banana"); set.add("mango"); // forEachRemaining()로 사용자 정의 출력 set.iterator().forEachRemaining((fruit) -> { System.out.printf("My favorite food is %s!\n", fruit); }); } }
- java.util.Iterator
반응형
'3학년 2학기 학사 > 소프트웨어 분석 및 설계' 카테고리의 다른 글
[소프트웨어 분석 및 설계] L23. Mediator Pattern, Memento Pattern, Observer Pattern (0) | 2024.12.13 |
---|---|
[소프트웨어 분석 및 설계] L22. State Pattern, Strategy Pattern (1) | 2024.12.13 |
[소프트웨어 분석 및 설계] L19. Facade Pattern, Flyweight Pattern, Proxy Pattern (0) | 2024.12.13 |
[소프트웨어 분석 및 설계] L18. Composite Pattern, Decorator Pattern (0) | 2024.12.13 |
[소프트웨어 분석 및 설계] L17. Adapter Pattern, Bridge Pattern (3) | 2024.12.13 |