[Design Pattern] 행위(Behavioral) 패턴

13. 책임 연쇄 패턴 (Chain Of Responsibility) 패턴

  • 요청을 보내는 쪽(sender)과 요청을 처리하는 쪽(receiver)의 분리하는 패턴
  • 클라이언트의 요청을 처리하기 위해 객체들을 체인 형태로 연결하여 결합력을 낮추기 위해 사용한다.
  • 핸들러 체인을 사용해서 요청을 처리한다.

구현 방법

// Handler
public abstract class RequestHandler {

  private RequestHandler nextHandler;

  public RequestHandler() {
    this(null);
  }

  public RequestHandler(RequestHandler nextHandler) {
    this.nextHandler = nextHandler;
  }

  public void handle(String request) {
    if (nextHandler != null) {
      nextHandler.handle(request);
    }
  }
  
}

// ConcreteHandler 1
public class Logging1RequestHandler extends RequestHandler {

  public LoggingRequestHandler(RequestHandler nextHandler) {
    super(nextHandler);
  }

  @Override
  public void handle(String request) {
    System.out.println("log1...");
    super.handle(request);
  }
  
}

// ConcreteHandler 2
public class Logging2RequestHandler extends RequestHandler {

  public LoggingRequestHandler(RequestHandler nextHandler) {
    super(nextHandler);
  }

  @Override
  public void handle(String request) {
    System.out.println("log2...");
    super.handle(request);
  }

}

// Client
public class Client {

  private RequestHandler requestHandler;

  public Client(RequestHandler requestHandler) {
    this.requestHandler = requestHandler;
  }

  public void invoke() {
    System.out.println("start...");
    requestHandler.handle("request");
  }
  
}
public static void main(String[] args) {
  RequestHandler chain = new Logging1RequestHandler(new Logging2RequestHandler());
  Client client = new Client(chain);
  client.invoke();
}

장단점

장점

  • 클라이언트 코드를 변경하지 않고 새로운 핸들러를 체인에 추가할 수 있다.
  • 각각의 체인은 자신이 해야하는 일만 한다.
  • 체인을 다양한 방법으로 구성할 수 있다.

단점

  • 디버깅 및 테스트가 쉽지 않을수 있다.
  • 체인이 적절하게 구성되어 있지 않을 경우 요청이 올바르게 처리되지 않을수있다.

사용하는곳

  • 자바 서블릿 필터
  • 스프링 시큐리티 필터

14. 커맨드 (Command) 패턴

  • 요청을 캡슐화 하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴.
  • 요청을 처리하는 방법이 바뀌더라도, 호출자의 코드는 변경되지 않는다.

구현 방법

// Invoker
public class Button {
  
  private Command command;
  
  public Button(Command command) {
    this.command = command;
  }
  
  public void press() {
    command.execute();
  }

  public void changeCommand(Command command) {
    this.command = command;
  }
  
}

// Command
public interface Command {
  void execute();
}

// Concrete Command 1
public class LightOnCommand implements Command {

  private final Light light;

  public LightOnCommand(Light light) {
    this.light = light;
  }

  @Override
  public void execute() {
    light.on();
  }

}

// Concrete Command 2
public class DoorOpenCommand implements Command {

  private final Door door;

  public DoorOpenCommand(Door door) {
    this.door = door;
  }

  @Override
  public void execute() {
    door.open();
  }

}

// Receiver 1
public class Light {

  public void on() {
    System.out.println("불을 켭니다.");
  }

}

// Receiver 2
public class Door {

  public void open() {
    System.out.println("문을 엽니다.");
  }

}
public static void main(String[] args) { 
  Button button = new Button(new LightOnCommand(new Light()));
  button.press();
  
  button.changeCommand(new DoorOpenCommand(new Door()));
  button.press();
}

장단점

장점

  • 기존 코드를 변경하지 않고 새로운 커맨드를 만들 수 있다.
  • 수신자의 코드가 변경되어도 호출자의 코드는 변경되지 않는다.
  • 커맨드 객체를 로깅, DB에 저장, 네트워크로 전송 하는 등 다양한 방법으로 활용할 수도 있다.

단점

  • 코드가 복잡하고 클래스가 많아진다.

사용하는곳

  • 자바 Runnable, 람다
  • 스프링 SimpleJdbcInsert, SimpleJdbcCall

15. 인터프리터 (Interpreter) 패턴

  • 자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴.
  • 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있다.

  • Expression
    • Abstract Syntax Tree(AST) 의 모든 노드에서 사용할 Interpret 작업을 정의한다.
  • Terminal Expression
    • 그 자체로 종료가 되는 Expression
  • NoNTerminal Expression
    • 재귀적으로 다른 Expression 을 참조하고 있는 Expression

구현 방법

// Expression
public interface Expression {
  boolean interpret(String context);
}

// TerminalExpression
public class TerminalExpression implements Expression {

  private String data;

  public TerminalExpression(String data) {
    this.data = data;
  }

  @Override
  public boolean interpret(String context) {
    return context.contains(data);
  }
  
}

// NonTerminalExpression 1
public class AndExpression implements Expression {

  private Expression expr1;
  private Expression expr2;

  public AndExpression(Expression expr1, Expression expr2) {
    this.expr1 = expr1;
    this.expr2 = expr2;
  }

  @Override
  public boolean interpret(String context) {
    return expr1.interpret(context) && expr2.interpret(context);
  }
  
}

// NonTerminalExpression 2
public class OrExpression implements Expression {

  private Expression expr1;
  private Expression expr2;

  public OrExpression(Expression expr1, Expression expr2) {
    this.expr1 = expr1;
    this.expr2 = expr2;
  }

  @Override
  public boolean interpret(String context) {
    return expr1.interpret(context) || expr2.interpret(context);
  }
  
}
public class Client {

  public static void main(String[] args) {
    Expression isMale = getMaleExpression();
    Expression isMarriedWoman = getMarriedWomanExpression();

    System.out.println("John is male? " + isMale.interpret("John male"));
    System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie"));
  }

  public static Expression getMaleExpression() {
    Expression robert = new TerminalExpression("Robert");
    Expression john = new TerminalExpression("John");
    return new OrExpression(robert, john);
  }

  public static Expression getMarriedWomanExpression() {
    Expression julie = new TerminalExpression("Julie");
    Expression married = new TerminalExpression("Married");
    return new AndExpression(julie, married);
  }
  
}

장단점

장점

  • 자주 등장하는 문제 패턴을 언어와 문법으로 정의할 수 있다.
  • 기존 코드를 변경하지 않고 새로운 Expression 을 추가할 수 있다.

단점

  • 복잡한 문법을 표현하려면 Expression 과 Parser 가 복잡해진다.

사용하는곳

  • Java 컴파일러, 정규표현식
  • SPEL (Spring Expression Language)

16. 반복자 (Iterator) 패턴

  • 집합 객체 내부 구조를 노출시키지 않고 순회 하는 방법을 제공하는 패턴.
  • 집합 객체를 순회하는 클라이언트 코드를 변경하지 않고 다양한 순회 방법을 제공할 수 있다.

  • Iterator: 컬렉션의 요소들을 순서대로 검색하기 위한 인터페이스
  • ConcreteIterator: iterator 인터페이스 구현체
  • Aggregate: 여러 요소들로 구성된 컬렉션 인터페이스
  • ConcreteAggregate: Aggregate 인터페이스 구현체

구현 방법

// Iterator
public interface Iterator<T> {
  boolean hasNext();
  T next();
}

// ConcreteIterator
public class BookIterator implements Iterator<Book> {
  
  private final BookAggregate bookAggregate;
  private int index;

  public BookIterator(BookAggregate bookAggregate) {
    this.bookAggregate = bookAggregate;
    this.index = 0;
  }

  @Override
  public boolean hasNext() {
    return index < bookAggregate.getLength();
  }

  @Override
  public Book next() {
    Book book = bookAggregate.getBookAt(index);
    index++;
    return book;
  }
  
}

// Aggregate
public interface Aggregate {
  Iterator<T> iterator();
}

// ConcreteAggregate
public class BookAggregate implements Aggregate {
  
  private final Book[] books;
  private int last = 0;

  public BookAggregate(int maxsize) {
    this.books = new Book[maxsize];
  }

  public Book getBookAt(int index) {
    return books[index];
  }

  public void appendBook(Book book) {
    this.books[last] = book;
    last++;
  }

  public int getLength() {
    return last;
  }

  @Override
  public Iterator<T> iterator() {
    return new BookIterator(this);
  }
  
}

// Target
public class Book {
  
  private final String name;

  public Book(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
  
}
public static void main(String[] args) {
  BookAggregate bookAggregate = new BookAggregate(3);
  bookAggregate.appendBook(new Book("자바"));
  bookAggregate.appendBook(new Book("파이썬"));
  bookAggregate.appendBook(new Book("golang"));

  Iterator<Book> iterator = bookAggregate.iterator();
  while(iterator.hasNext()){
    System.out.println(iterator.next().getName());
  }
}

장단점

장점

  • 집합 객체가 가지고 있는 객체들에 손쉽게 접근할 수 있다.
  • 일관된 인터페이스를 사용해 여러 형태의 집합 구조를 순회할 수 있다

단점

  • 클래스가 늘어나고 복잡도가 증가한다.

사용하는곳

  • Java, Enumeration, Iterator
  • Java StAX (Streaming API for XML)의 Iterator 기반 API
    • XmlEventReader, XmlEventWriter
  • Spring, CompositeIterator

17. 중재자 (Mediator) 패턴

  • 여러 객체들이 소통하는 방법을 캡슐화하는 패턴.
  • 여러 컴포넌트간의 결합도를 중재자를 통해 낮출 수 있다.
  • M:N의 관계에서 M:1의 관계로 복잡도를 떨어뜨려 유지 보수 및 재사용, 확장성에 유리한 패턴이다.

구현 방법

// Mediator
public interface Mediator {
  void addUser(Colleague user);
  void sendMessage(String message, Colleague user);
}

// ConcreteMediator
public class ConcreteMediator implements Mediator {

  private final List<Colleague> users;

  public ConcreteMediator() {
    this.users = new ArrayList<>();
  }

  @Override
  public void addUser(Colleague user) {
    this.users.add(user);
  }

  @Override
  public void sendMessage(String message, Colleague user) {
    for (Colleague u : this.users) {
      if (u != user) {
        u.receive(message);
      }
    }
  }
  
}

// Colleague
public abstract class Colleague {

  protected Mediator mediator;
  protected String name;

  public Colleague(Mediator mediator, String name) {
    this.mediator = mediator;
    this.name = name;
  }

  public abstract void send(String msg);

  public abstract void receive(String msg);
}

// ColleagueA
public class ConcreteColleague extends Colleague {

  public ConcreteColleague(Mediator mediator, String name) {
    super(mediator, name);
  }

  @Override
  public void send(String msg) {
    System.out.println(this.name + ": Sending Message=" + msg);
    mediator.sendMessage(msg, this);
  }

  @Override
  public void receive(String msg) {
    System.out.println(this.name + ": Received Message:" + msg);
  }
  
}
public static void main(String[] args) {
  Mediator mediator = new ConcreteMediator();
  
  Colleague user1 = new ConcreteColleague(mediator, "aaa");
  Colleague user2 = new ConcreteColleague(mediator, "bbb");
  Colleague user3 = new ConcreteColleague(mediator, "ccc");
  Colleague user4 = new ConcreteColleague(mediator, "ddd");
  
  mediator.addUser(user1);
  mediator.addUser(user2);
  mediator.addUser(user3);
  mediator.addUser(user4);

  user1.send("message....");
}

장단점

장점

  • 컴포넌트 코드를 변경하지 않고 새로운 중재자를 만들어 사용할 수 있다.
  • 각각의 컴포넌트 코드를 보다 간결하게 유지할 수 있다.

단점

  • 중재자 역할을 하는 클래스의 복잡도와 결합도가 증가한다.

사용하는곳

  • Java, ExecutorService, Executor
  • Spring, DispatcherServlet

퍼사드 패턴 vs 중재자 패턴

  • 퍼사드 패턴은 서브 시스템 의존성을 간단한 인터페이스로 추상화하여 상호 작용을 캡슐화 하므로 단방향이다.
  • 중재자 패턴은 여러 컴포넌트간의 결합도를 중재자를 통해 낮추는것으로 양방향이다.

18. 메멘토 (Memento) 패턴

  • 캡슐화를 유지하면서 객체 내부 상태를 외부에 저장하는 방법.
  • 객체 상태를 외부에 저장했다가 해당 상태로 다시 복구할 수 있다.
  • 토큰이라고 보면 될 것 같다.

  • CareTaker
    • Memento 의 보관을 책임지며 메멘토의 내용을 검사하거나 그 내용을 건드리지는 않는다.
  • Originator
    • 자체적으로 현재의 state 를 저장하는 Memento 객체를 생성할 수 있다.
    • state 를 복원하기 위해 Memento 를 사용한다.
  • Memento
    • Originator 의 state 에 대한 스냅샷 역할을 하는 객체
    • 특정 시점의 상태 정보를 저장하므로 Immutable 해야함.

구현 방법

// CareTaker
public class Storage {
  
  private final Stack<GameSave> history = new Stack<>();

  public void save(GameSave gameSave) {
    history.push(gameSave);
  }

  public GameSave load() {
    if (!stack.empty()) {
      return history.pop();
    }
    throw new IllegalStateException(); 
  }
  
}

// Originator
public class Game {

  private int level;
  private int score;
  
  public int getLevel() {
    return level;
  }
  
  public void setLevel(int level) {
    this.level = level;
  }

  public int getScore() {
    return score;
  }
  
  public void setScore(int score) {
    this.score = score;
  }

  public GameSave save() {
    return new GameSave(level, score);
  }

  public void restore(GameSave gameSave) {
    this.level = gameSave.getLevel();
    this.score = gameSave.getScore();
  }

}

// Memento
public final class GameSave {

  private final int level;
  private final int score;

  public GameSave(int level, int score) {
    this.level = level;
    this.score = score;
  }

  public int getLevel() {
    return level;
  }

  public int getScore() {
    return score;
  }

}
public static void main(String[] args) {
  Storage storage = new Storage();
  
  Game game = new Game();
  game.setLevel(100);
  game.setScore(3);

  GameSave gameSave = game.save();
  storage.save(gameSave);

  game.setLevel(200);
  game.setScore(6);

  GameSave loadedGameSave = storage.load();
  game.restore(loadedGameSave);

  System.out.println(game.getLevel()); // 100
  System.out.println(game.getScore()); // 3
}

장단점

장점

  • 캡슐화를 지키면서 상태 객체 상태 스냅샷을 만들 수 있다.
  • 객체 상태 저장하고 또는 복원하는 역할을 CareTaker 에게 위임할 수 있다.
  • 객체 상태가 바뀌어도 클라이언트 코드는 변경되지 않는다.

단점

  • 많은 정보를 저장하는 Memento 를 자주 생성하는 경우 메모리 사용량에 많은 영향을 줄 수 있다.

19. 옵저버 (Observer) 패턴

  • 다수의 객체가 특정 객체 상태 변화를 감지하고 알림을 받는 패턴.
  • 발행(publish)-구독(subscribe) 패턴을 구현할 수 있다.

구현 방법

// Subject
public class ChatServer {

  private Map<String, List<Subscriber>> subscribers = new HashMap<>();

  public void register(String subject, Subscriber subscriber) {
    if (this.subscribers.containsKey(subject)) {
      this.subscribers.get(subject).add(subscriber);
    } else {
      List<Subscriber> list = new ArrayList<>();
      list.add(subscriber);
      this.subscribers.put(subject, list);
    }
  }

  public void unregister(String subject, Subscriber subscriber) {
    if (this.subscribers.containsKey(subject)) {
      this.subscribers.get(subject).remove(subscriber);
    }
  }

  public void sendMessage(User sender, String subject, String message) {
    if (this.subscribers.containsKey(subject)) {
      String userMessage = "[ send ] " + sender.getName() + ": " + message;
      this.subscribers.get(subject).forEach(s -> s.handleMessage(userMessage));
    }
  }

}

// Observer
public interface Subscriber {
  void handleMessage(String message);
}

// ConcreteObserver
public class User implements Subscriber {

  private String name;

  public User(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  @Override
  public void handleMessage(String message) {
    System.out.println("[handle] " + name + ": " + message);
  }
  
}
public static void main(String[] args) {
  ChatServer chatServer = new ChatServer();
  
  User user1 = new User("aaa");
  User user2 = new User("bbb");

  chatServer.register("subject1", user1);
  chatServer.register("subject1", user2);

  chatServer.register("subject2", user1);
  chatServer.register("subject2", user2);

  chatServer.sendMessage(user1, "subject1", "subject1 + message1...");
  chatServer.sendMessage(user2, "subject1", "subject1 + message2...");

  chatServer.unregister("subject2", user1);
    
  chatServer.sendMessage(user2, "subject2", "subject2 + message3...");
}
>> console <<
[ send ] aaa: subject1 + message1...
[handle] aaa: subject1 + message1...
[handle] bbb: subject1 + message1...
[ send ] bbb: subject1 + message2...
[handle] aaa: subject1 + message2...
[handle] bbb: subject1 + message2...
[ send ] bbb: subject2 + message3..
[handle] bbb: subject2 + message3...

장단점

장점

  • 상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subscriber)의 관계를 느슨하게 유지할 수 있다.
  • Subject 의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있다.
  • 런타임에 옵저버를 추가하거나 제거할 수 있다.

단점

  • 복잡도가 증가한다.
  • 다수의 Observer 객체를 등록 이후 해지 않는다면 memory leak 이 발생할 수도 있다

사용하는곳

  • Java, Observable 과 Observer (자바 9부터 deprecated)
  • Java 9 이후, PropertyChangeListener, PropertyChangeEvent
  • Java 9 이후, Flow API
  • Spring, ApplicationContext ApplicationEvent

20. 상태 (State) 패턴

  • 객체 내부 상태 변경에 따라 객체의 행동이 달라지는 패턴.
  • 상태에 특화된 행동들을 분리해 낼 수 있으며, 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.
  • 상태를 객체화 하여 상태가 행동을 할 수 있도록 위임한다.
  • 상태 처리 방법
    1. State 에 Context 를 필드로 주입받는다.
    2. State 메서드에 Context 를 전달한다.
    3. State 메서드에서 처리후에 다른 State 를 리턴한다.

구현 방법

// Context
public class Light {
  
  private State state;

  public Light() {
    this.state = new Off();
  }

  public void press() {
    this.state = state.press();
  }
  
  public void changeState(State state) {
    this.state = state;
  }
  
}

// State
public interface State {
  State press();
}

// ConcreteState 1
public class On implements State {
  
  public State press() {
    System.out.println("On -> Off");
    return new Off();
  }
  
}

// ConcreteState 2
public class Off implements State {

  public State press() {
    System.out.println("Off -> On");
    return new On();
  }

}
public static void main(String[] args) {
  Light light = new Light();     // State: Off
  light.press();  // Off -> On 출력, State: On
  light.press();  // On -> Off 출력, State: Off
  
  light.changeState(new On());   // State: On
  light.press();  // On -> Off 출력, State: Off
}

장단점

장점

  • 상태에 따른 동작을 개별 클래스로 옮겨서 관리할 수 있다.
  • 기존의 특정 상태에 따른 동작을 변경하지 않고 새로운 상태에 다른 동작을 추가할 수 있다.
  • 코드 복잡도를 줄일 수 있다

단점

  • 복잡도가 증가한다.

21. 전략 (Strategy) 패턴

  • 여러 알고리즘을 캡슐화하고 상호 교환 가능하게 만드는 패턴.
  • 컨텍스트에서 사용할 알고리즘을 클라이언트 선택한다.
  • Client 가 Strategy 를 주입해주는 방식
    • 생성자를 통해 주입
    • 메서드 파라미터로 주입

구현 방법

// Context
public class Car {
  
  public void drive(Speed speed) {
    speed.drive();
  }
  
}

// Strategy
public interface Speed {
  void drive();
}

// ConcreteStrategy 1
public class Normal implements Speed {
  
  @Override
  public void drive() {
    System.out.println("drive speed: normal...");
  }
  
}

// ConcreteStrategy 2
public class Slow implements Speed {

  @Override
  public void drive() {
    System.out.println("drive speed: Slow...");
  }

}
public static void main(String[] args) {
    Car car = new Car();
    
    car.drive(new Normal());
    car.drive(new Slow());
}

장단점

장점

  • 새로운 전략을 추가하더라도 기존 코드를 변경하지 않는다.
  • 상속 대신 위임을 사용할 수 있다.
  • 런타임에 전략을 변경할 수 있다.

단점

  • 복잡도가 증가한다.
  • 클라이언트 코드가 구체적인 전략을 알아야 한다.

사용하는곳

  • Java Comparator
  • Spring ApplicationContext

커맨드 패턴 vs 전략 패턴

  • 커맨드 패턴은 무엇 에 초점을 둔다. 어떻게 할지는 외부에서 주입해주고 무엇을 할지를 고려한다.
  • 전략 패턴은 어떻게 에 초점을 둔다. 하고자 하는것은 정해져있고 방법을 어떻게 할지 고려한다.

상태 패턴 vs 전략 패턴

  • 상태 패턴에서 상태는 스스로를 다른 상태로 변경할 수 있지만, 전략 패턴에서 이는 불가능하다.

22. 템플릿 메서드 (Template Method) 패턴

  • 알고리즘 구조를 서브 클래스가 확장할 수 있도록 템플릿으로 제공하는 방법.
  • 추상 클래스는 템플릿을 제공하고 하위 클래스는 구체적인 알고리즘을 제공한다.

구현 방법

// AbstractClass
public abstract class AbstractClass {
  
  public void execute() {
    step1();
    templateMethod();
    step2();
  }

  public void step1() { ... }
  public void step2() { ... }
  
  protected abstract void templateMethod();
  
}

// ConcreteClass 1
public class ConcreteClass1 extends AbstractClass {
  
  @Override
  protected void templateMethod() {
    // do something...
  }
  
}

// ConcreteClass 2
public class ConcreteClass2 extends AbstractClass {

  @Override
  protected void templateMethod() {
    // do something...
  }

}
public static void main(String[] args) {
    AbstractClass concrete1 = new ConcreteClass1();
    AbstractClass concrete2 = new ConcreteClass2();

    concrete1.execute();
    concrete2.execute();
}

장단점

장점

  • 템플릿 코드를 재사용하고 중복 코드를 줄일 수 있다.
  • 템플릿 코드를 변경하지 않고 상속을 받아서 구체적인 알고리즘만 변경할 수 있다.

단점

  • 리스코프 치환 원칙을 위반할 수도 있다.
    • sub class 에서 동작을 변경할수있기 때문
  • 알고리즘 구조가 복잡할 수록 템플릿을 유지하기 어려워진다.

사용하는곳

  • Java, HttpServlet
  • Spring
    • 템플릿 메서드 패턴: Security 설정과 같은 Configuration
    • 템플릿 콜백 패턴: JdbcTemplate, RestTemplate

템플릿 콜백 (Template-Callback) 패턴

  • 상속 대신 익명 내부 클래스 또는 람다 표현식을 활용할 수 있다.
  • 전략 패턴과 거의 유사하며 전략을 익명 내부 클래스 또는 람다 표현식으로 사용하는것만 차이가 있다.
    • ConcreteCallback 을 만들지 않고 익명 내부 클래스 또는 람다 표현식을 사용하면 된다.

23. 방문자 (Visitor) 패턴

  • 기존 코드를 변경하지 않고 새로운 기능을 추가하는 방법.
  • 더블 디스패치 (Double Dispatch)를 활용할 수 있다.
  • 알고리즘을 객체 구조에서 분리시키는 디자인 패턴이다.
    • 실제 로직을 가지고 있는 객체(Visitor)가 로직을 적용할 객체(Element)를 방문하면서 실행된다.
  • Element 가 추가될 가능성이 적고 Visitor 추가가 흔할 경우 유용하다.

구현 방법

// Element
public interface Shape {
  void accept(Device device);
}

// Element A

public class Triangle implements Shape {
  @Override
  public void accept(Device device) {
    device.print(this);
  }
}

// Element B
public class Rectangle implements Shape {
  @Override
  public void accept(Device device) {
    device.print(this);
  }
}

// Visitor
public interface Device {
  void print(Rectangle rectangle);
  void print(Triangle triangle);
}

// ConcreteVisitor 1
public class Phone implements Device {

  @Override
  public void print(Rectangle rectangle) {
    System.out.println("Print Rectangle to Phone");

  }

  @Override
  public void print(Triangle triangle) {
    System.out.println("Print Triangle to Phone");
  }
  
}

// ConcreteVisitor 2
public class Pad implements Device {
  
  @Override
  public void print(Rectangle rectangle) {
    System.out.println("Print Rectangle to Pad");
  }

  @Override
  public void print(Triangle triangle) {
    System.out.println("Print Triangle to Pad");
  }
  
}
public static void main(String[] args) {
  Shape triangle = new Triangle();
  Device phone = new Phone();
  triangle.accept(phone);

  Shape rectangle = new Rectangle();
  Device pad = new Pad();
  rectangle.accept(pad);
}

장단점

장점

  • 기존 코드를 변경하지 않고 새로운 코드를 추가할 수 있다.
  • 추가 기능을 한 곳에 모아둘 수 있다.

단점

  • 복잡하다.
  • 새로운 Element 를 추가하거나 제거할 때 모든 Visitor 코드를 변경해야 한다.

사용하는곳

  • Java FileVisitor, SimpleFileVisitor, AnnotationValueVisitor ElementVisitor
  • Spring BeanDefinitionVisitor

좋은 웹페이지 즐겨찾기