[GoF 디자인 패턴] 인터프리터(Interpreter) 패턴, 이터레이터 (Interator) 패턴, 중재자 패턴(Mediator)

JMM·2025년 1월 11일
0

GoF 디자인 패턴

목록 보기
8/11
post-thumbnail

1. 인터프리터(Interpreter) 패턴 : 자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴.

인터프리터 패턴은 반복적으로 등장하는 문제를 해결하기 위해 문제를 언어(문법)로 정의하고, 이를 기반으로 해석(interpret)하여 해결하는 패턴이다.

주로 간단한 문법이나 계산식을 처리하는 데 사용되며, 언어를 구성하는 요소를 클래스로 구현한다.


Before

PostfixNotation

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+-))
  1. 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());
}

문제점

  1. 확장성 부족:
    • 연산자(+, -, *, 등)를 추가하려면 calculate() 메서드를 수정해야 함.
  2. 재사용성 부족:
    • 계산 로직이 한 클래스에 집중되어 있어, 다른 곳에서 사용하기 어려움.
  3. 문법 표현 부족:
    • 후위 표기법의 구조를 객체로 나타내지 못함.

After (인터프리터 패턴 적용)

변경 내용

  1. 표현(Expression)을 객체화:

    • 계산식의 각 요소(숫자, 연산자 등)를 표현하는 클래스를 구현.
    • 모든 표현 클래스는 PostfixExpression 인터페이스를 구현.
  2. 확장성 증가:

    • 새로운 연산자를 추가하려면 해당 연산에 대한 표현 클래스를 추가하면 됨.
  3. Parser 도입:

    • 수식을 분석(Parsing)하여 객체로 변환하는 PostfixParser 추가.

구조

  1. PostfixExpression 인터페이스:
    • 모든 표현 클래스의 공통 인터페이스.
    • interpret(Map<Character, Integer> context) 메서드로 계산 수행.
public interface PostfixExpression {
    int interpret(Map<Character, Integer> context);
}
  1. 구현 클래스
    • 각 연산자 및 변수는 독립적인 클래스로 구현.

숫자(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);
    }
}
  1. PostfixParser:
    • 문자열 수식을 분석(Parsing)하여 객체 트리로 변환.
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);
        }
    }
}
  1. 클라이언트 코드:
    • 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. 문자열 분석:

  • PostfixParser.parse("xyz+-a+")로 문자열 분석.

2. 객체 트리 생성:

  • VariableExpression, PlusExpression, MinusExpression 객체 생성.

3. 해석 및 계산:

  • PostfixExpression.interpret(context) 호출로 계산 수행.
  • Map<Character, Integer> context = Map.of('x', 1, 'y', 2, 'z', 3, 'a', 4)에 따라 값 해석.

4. 결과 출력:

  • System.out.println(result); // 출력: 0

다이어그램

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);
    }
}
  • 설명:
    • 이 코드 자체는 컴파일러에 의해 해석되고 실행된다.
    • 자바 컴파일러는 소스 코드를 읽고, 문법을 해석하여 바이트코드(.class 파일)로 변환.

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은 스프링에서 제공하는 표현 언어로, 동적으로 값을 참조하거나 계산식을 평가할 수 있다.

예제 코드 1: 기본 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;
    }
}
  • 설명:
    • SpelExpressionParsertitle이라는 속성을 해석.
    • 런타임에 객체(Book)의 속성 값을 동적으로 참조.

예제 코드 2: @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라는 표현식을 해석하여 결과 값을 필드에 주입.
  • 서버 구동시 7이라는 값이 찍힌다.

2. 이터레이터 (Interator) 패턴 : 집합 객체 내부 구조를 노출시키지 않고 순회 하는 방법을 제공하는 패턴.

집합 객체를 순회하는 클라이언트 코드를 변경하지 않고 다양한 순회 방법을 제공할 수 있다.

클라이언트 코드와 분리하여, 집합 객체의 구조와 상관없이 일관된 방식으로 객체를 탐색할 수 있다.


Before


Board

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));
    }
}

Post

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;
    }
}

Client

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());
        }
    }

}

구조 및 문제점

  1. Board 클래스:
    • 게시글을 관리하는 리스트(posts) 제공.
    • addPost()로 게시글 추가.
  2. 문제점:
    • 순회 로직이 클라이언트 코드에 존재:
      • 들어간 순서로 순회: 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 클래스 모두 수정해야 한다.

After (이터레이터 패턴 적용)

  1. Iterator 인터페이스 활용
  • 순회 방식은 별도의 클래스(RecentPostIterator)로 분리.
  • 클라이언트는 Iterator 인터페이스를 통해 순회.
  1. Board 클래스 변경

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());
}
  1. 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. 게시글 추가:

  • Client는 Board.addPost()를 호출하여 게시글을 추가.

2. 순회 이터레이터 생성:

  • Client는 Board.getRecentPostIterator()를 호출하여 최신순으로 게시글을 순회할 수 있는 RecentPostIterator 객체를 획득.

3.게시글 순회:

  • RecentPostIterator.hasNext()와 RecentPostIterator.next()를 사용하여 최신순으로 게시글을 하나씩 순회.

이터레이터 패턴 장단점

장점

  1. 내부 구조 캡슐화:

    • 집합 객체의 내부 구조를 노출하지 않고, 클라이언트가 객체를 탐색 가능.
  2. 유연한 순회 방법 제공:

    • 다양한 이터레이터를 통해 다른 순회 방법을 쉽게 구현.
  3. 일관된 인터페이스:

    • 클라이언트는 Iterator 인터페이스를 사용하여 다양한 집합 구조를 동일하게 처리.

단점

  1. 클래스 증가:

    • 각 순회 방식마다 별도의 이터레이터 클래스를 구현해야 함.
  2. 복잡도 증가:

    • 순회 방식이 많아질수록 구현이 복잡해질 수 있음.

이터레이터 패턴, 실무에서는?

A. 자바

  1. java.util.Iterator
    • 대부분의 자바 컬렉션(예: List, Set, Map)에서 제공.
List<String> list = Arrays.asList("A", "B", "C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
  1. java.util.Enumeration

    • 구형 API에서 사용되는 이터레이터와 유사한 구조.
  2. Java StAX:

    • XML 파서에서 이터레이터 기반 API 제공.
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());
    }
}

B. 스프링

  1. CompositeIterator
    • 여러 이터레이터를 묶어서 하나의 이터레이터로 제공.

3. 중재자 패턴 : 여러 객체들이 소통하는 방법을 캡슐화하는 패턴.

중재자 패턴은 여러 객체가 직접적으로 소통하지 않고, 중재자(Mediator)를 통해 간접적으로 소통하게 만드는 패턴이다.

여러 컴포넌트간의 결합도를 중재자를 통해 낮출 수 있다.

Colleague는 구체적인 클래스에 의존하지 않고 인터페이스인 Mediator만 알면 된다!

이룰 통해
1) 객체 간의 복잡한 의존성을 제거하고 결합도를 낮추고,
2) 중앙 중재자(Mediator) 클래스가 커뮤니케이션을 관리하도록 한다!


Before

문제점

  1. 클래스 간 강한 결합
  • GuestRestaurantCleaningService를 직접 참조.
  • RestaurantCleaningService를 직접 참조.
  • 객체 간 관계가 복잡하고, 변경 시 여러 클래스에 영향을 미침.
  1. 확장성 부족
  • 새로운 기능 추가 시, 기존 클래스에 변경 필요.

Before 코드

Hotel

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 호출
    }
}

Guest

  • RestaurantCleaningService를 직접 호출.

    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);
        }
    }

Restaurant

  • 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);
        }
    }

CleaningService

  • GuestRestaurant를 직접 처리.

    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);
        }
    }

Before 코드 로직의 문제점

  • 클래스 간 직접적인 의존성으로 인해 변경이 어려움.
  • 새로운 역할이 추가되면 관련 클래스 모두 수정 필요.

After

개선 사항

  1. FrontDesk 중재자 도입:

    • 모든 커뮤니케이션은 FrontDesk를 통해 이루어짐.
    • Guest, Restaurant, CleaningService 간의 결합도 제거.
  2. 클래스 역할 분리:

    • 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);
    }
}

다이어그램

추가적으로 현재 예제 같은 경우, 여러 중재자가 아니고, 중재자를 교체할 필요가 없기 때문에 인터페이스가 아닌 단일 클래스로 설계하였다. 만약 다양한 중재자가 필요하거나, 확장성이 요구될 때 인터페이스를 도입할 필요가 있을 것 같다!


중재자(Mediator) 패턴 장단점


장점

  1. 컴포넌트 코드 변경 없이 새로운 중재자 생성 가능

    • 컴포넌트들은 중재자에 의존하므로, 새로운 중재자를 도입해도 기존 컴포넌트 코드는 변경할 필요가 없다!
  2. 컴포넌트 코드 간결화

    • 컴포넌트들은 서로를 직접 참조하지 않고 중재자만 참조하기 때문에, 각 컴포넌트의 책임이 단순해지고 코드가 간결해진다.

단점

  1. 중재자 클래스의 복잡도 증가

    • 중재자 클래스가 모든 요청과 의사소통을 처리해야 하므로 코드가 비대해질 수 있다.
  2. 중재자에 의존성 집중

    • 시스템의 모든 의존성이 중재자에 집중되므로, 중재자의 변경이 전체 시스템에 영향을 미칠 가능성이 있다.

중재자 패턴, 실무에서는?

1. 자바에서의 활용

  • ExecutorService, Executor:
    • 작업(Tasks)을 실행하는 중재자 역할.
    • 예: 여러 작업(Thread)을 관리하고 실행을 조율.

2. 스프링에서의 활용

  • DispatcherServlet:
    • 스프링 MVC에서 중앙 중재자 역할.
    • 클라이언트의 HTTP 요청을 받아 적절한 컨트롤러로 라우팅.

코드 예제

public class MediatorInSpring {

    public static void main(String[] args) {
        DispatcherServlet dispatcherServlet;
    }
}
  • 설명:
    • DispatcherServlet은 중재자 역할을 수행하며, HTTP 요청을 수신하고 적절한 컨트롤러에 전달.
    • 스프링 MVC 아키텍처에서 클라이언트와 컨트롤러 사이의 의존성을 완전히 제거한다.

출처 : 코딩으로 학습하는 GoF의 디자인 패턴

0개의 댓글