1. 책임 연쇄(Chain-of-Responsibility) 패턴
1.1 패턴 소개

요청을 보내는 쪽(sender)과 요청을 처리하는 쪽(receiver)의 분리하는 패턴
- 핸들러 체인을 사용해서 요청을 처리한다

1.2 예시 - 기존
1// Request.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;34public class Request {56private String body;78public Request(String body) {9this.body = body;10}1112public String getBody() {13return body;14}1516public void setBody(String body) {17this.body = body;18}19}
1// RequestHandler.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;34public class RequestHandler {56public void handler(Request request) {7System.out.println(request.getBody());8}9}
1// AuthRequestHandler.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;34public class AuthRequestHandler extends RequestHandler {56public void handler(Request request) {7System.out.println("인증이 되었나?");8System.out.println("이 핸들러를 사용할 수 있는 유저인가?");9super.handler(request);10}11}
1// LoggingRequestHandler.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;34public class LoggingRequestHandler extends RequestHandler {56@Override7public void handler(Request request) {8System.out.println("로깅");9super.handler(request);10}11}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;34public class Client {56public static void main(String[] args) {7Request request = new Request("무궁화 꽃이 피었습니다.");8RequestHandler requestHandler = new LoggingRequestHandler();9requestHandler.handler(request);10}11}
1.3 예시 - 변경
1// RequestHandler.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;56public abstract class RequestHandler {78private RequestHandler nextHandler;910public RequestHandler(RequestHandler nextHandler) {11this.nextHandler = nextHandler;12}1314public void handle(Request request) {15if (nextHandler != null) {16nextHandler.handle(request);17}18}19}
1// PrintRequestHandler.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;56public class PrintRequestHandler extends RequestHandler {78public PrintRequestHandler(RequestHandler nextHandler) {9super(nextHandler);10}1112@Override13public void handle(Request request) {14System.out.println(request.getBody());15super.handle(request);16}17}
1// LoggingRequestHandler.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;56public class LoggingRequestHandler extends RequestHandler {78public LoggingRequestHandler(RequestHandler nextHandler) {9super(nextHandler);10}1112@Override13public void handle(Request request) {14System.out.println("로깅");15super.handle(request);16}17}
1// AuthRequestHandler.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;56public class AuthRequestHandler extends RequestHandler {78public AuthRequestHandler(RequestHandler nextHandler) {9super(nextHandler);10}1112@Override13public void handle(Request request) {14System.out.println("인증이 되었는가?");15super.handle(request);16}17}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;56public class Client {78private RequestHandler requestHandler;910public Client(RequestHandler requestHandler) {11this.requestHandler = requestHandler;12}1314public void doWork() {15Request request = new Request("이번 놀이는 뽑기입니다.");16requestHandler.handle(request);17}1819public static void main(String[] args) {20RequestHandler chain = new AuthRequestHandler(21new LoggingRequestHandler(new PrintRequestHandler(null))22);23Client client = new Client(chain);24client.doWork();25}26}
1.4 장단점
- 장점
- 클라이언트 코드를 변경하지 않고 새로운 핸들러를 체인에 추가할 수 있다.
- 각각의 체인은 자신이 해야하는 일만 한다.
- 체인을 다양한 방법으로 구성할 수 있다.
- 단점
- 디버깅이 조금 어렵다
1.5 실무에서 어떻게 쓰이나?
- 자바 : 서블릿 필터
1// CoRInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;34import java.io.IOException;5import javax.servlet.*;67public class CoRInJava {89public static void main(String[] args) {10Filter filter = new Filter() {11@Override12public void doFilter(13ServletRequest request,14ServletResponse response,15FilterChain chain16) throws IOException, ServletException {17// TODO 전처리18chain.doFilter(request, response);19// TODO 후처리20}21};22}23}
1// MyFilter.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;34import java.io.IOException;5import javax.servlet.*;6import javax.servlet.annotation.WebFilter;78@WebFilter(urlPatterns = "/hello")9public class MyFilter implements Filter {1011@Override12public void doFilter(13ServletRequest request,14ServletResponse response,15FilterChain chain16) throws IOException, ServletException {17System.out.println("게임에 참하신 여러분 모두 진심으로 환영합니다.");18chain.doFilter(request, response);19System.out.println("꽝!");20}21}
1// App.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;34import org.springframework.boot.SpringApplication;5import org.springframework.boot.autoconfigure.SpringBootApplication;6import org.springframework.boot.web.servlet.ServletComponentScan;78@ServletComponentScan9@SpringBootApplication10public class App {1112public static void main(String[] args) {13SpringApplication.run(App.class, args);14}15}
1// HelloController.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;34import org.springframework.web.bind.annotation.GetMapping;5import org.springframework.web.bind.annotation.RestController;67@RestController8public class HelloController {910@GetMapping("/hello")11public String hello() {12return "hello";13}14}

- 스프링 : 스프링 시큐리티 필터
1// SecurityConfig.java2package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;34import org.springframework.context.annotation.Configuration;5import org.springframework.security.config.annotation.web.builders.HttpSecurity;6import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;78@Configuration9public class SecurityConfig extends WebSecurityConfigurerAdapter {1011@Override12protected void configure(HttpSecurity http) throws Exception {13http.authorizeRequests().anyRequest().permitAll().and();14}15}
2. 커맨드(Command) 패턴
2.1 패턴 소개

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

2.2 예시 - 기존
1// Light.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;34public class Light {56private boolean isOn;78public void on() {9System.out.println("불을 켭니다.");10this.isOn = true;11}1213public void off() {14System.out.println("불을 끕니다.");15this.isOn = false;16}1718public boolean isOn() {19return this.isOn;20}21}
1// Game.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;34public class Game {56private boolean isStarted;78public void start() {9System.out.println("게임을 시작합니다.");10this.isStarted = true;11}1213public void end() {14System.out.println("게임을 종료합니다.");15this.isStarted = false;16}1718public boolean isStarted() {19return isStarted;20}21}
1// Button.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;34public class Button {56private Light light;78public Button(Light light) {9this.light = light;10}1112public void press() {13light.off();14}1516public static void main(String[] args) {17Button button = new Button(new Light());18button.press();19button.press();20button.press();21button.press();22}23}
1// MyApp.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;34public class MyApp {56private Game game;78public MyApp(Game game) {9this.game = game;10}1112public void press() {13game.start();14}1516public static void main(String[] args) {17Button button = new Button(new Light());18button.press();19button.press();20button.press();21button.press();22}23}
2.3 예시 - 변경
1// Command.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;34public interface Command {5void execute();67void undo();8}
1// GameEndCommand.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;56public class GameEndCommand implements Command {78private Game game;910public GameEndCommand(Game game) {11this.game = game;12}1314@Override15public void execute() {16game.end();17}1819@Override20public void undo() {21new GameStartCommand(this.game).execute();22}23}
1// GameStartCommand.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;56public class GameStartCommand implements Command {78private Game game;910public GameStartCommand(Game game) {11this.game = game;12}1314@Override15public void execute() {16game.start();17}1819@Override20public void undo() {21new GameEndCommand(this.game).execute();22}23}
1// LightOffCommand.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;56public class LightOffCommand implements Command {78private Light light;910public LightOffCommand(Light light) {11this.light = light;12}1314@Override15public void execute() {16light.off();17}1819@Override20public void undo() {21new LightOnCommand(this.light).execute();22}23}
1// LightOnCommand.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;56public class LightOnCommand implements Command {78private Light light;910public LightOnCommand(Light light) {11this.light = light;12}1314@Override15public void execute() {16light.on();17}1819@Override20public void undo() {21new LightOffCommand(this.light).execute();22}23}
1// Button.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;34import java.util.Stack;5import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;6import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;78public class Button {910private Stack<Command> commands = new Stack<>();1112public void press(Command command) {13command.execute();14commands.push(command);15}1617public void undo() {18if (!commands.isEmpty()) {19Command command = commands.pop();20command.undo();21}22}2324public static void main(String[] args) {25Button button = new Button();26button.press(new GameStartCommand(new Game()));27button.press(new LightOnCommand(new Light()));28button.undo();29button.undo();30}31}
1// MyApp.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;34import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;56public class MyApp {78private Command command;910public MyApp(Command command) {11this.command = command;12}1314public void press() {15command.execute();16}1718public static void main(String[] args) {19MyApp myApp = new MyApp(new GameStartCommand(new Game()));20}21}
2.4 장단점
- 장점
- 기존 코드를 변경하지 않고 새로운 커맨드를 만들 수 있다.
- 수신자의 코드가 변경되어도 호출자의 코드는 변경되지 않는다.
- 커맨드 객체를 로깅, DB에 저장, 네트워크로 전송 하는 등 다양한 방법으로 활용할 수도 있다.
- 단점 : 코드가 복잡하고 클래스가 많아진다
2.5 실무에서 어떻게 쓰이나?
자바
- Runnable
- 람다
- 메소드 레퍼런스
1// CommandInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._03_java;34import java.util.concurrent.ExecutorService;5import java.util.concurrent.Executors;6import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;7import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;89public class CommandInJava {1011public static void main(String[] args) {12Light light = new Light();13Game game = new Game();14ExecutorService executorService = Executors.newFixedThreadPool(4);15executorService.submit(light::on);16executorService.submit(game::start);17executorService.submit(game::end);18executorService.submit(light::off);19executorService.shutdown();20}21}
스프링
- SimpleJdbcInsert
- SimpleJdbcCall
1// CommandInSpring.java2package me.whiteship.designpatterns._03_behavioral_patterns._14_command._03_java;34import java.time.LocalDateTime;5import java.util.HashMap;6import java.util.Map;7import javax.sql.DataSource;8import me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after.Command;9import org.springframework.jdbc.core.simple.SimpleJdbcInsert;1011public class CommandInSpring {1213private DataSource dataSource;1415public CommandInSpring(DataSource dataSource) {16this.dataSource = dataSource;17}1819public void add(Command command) {20SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource)21.withTableName("command")22.usingGeneratedKeyColumns("id");2324Map<String, Object> data = new HashMap<>();25data.put("name", command.getClass().getSimpleName());26data.put("when", LocalDateTime.now());27insert.execute(data);28}29}
3. 인터프리터(Interpreter) 패턴
3.1 패턴 소개

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

3.2 예시 - 기존
1// PostfixNotation.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._01_before;34import java.util.Stack;56public class PostfixNotation {78private final String expression;910public PostfixNotation(String expression) {11this.expression = expression;12}1314public static void main(String[] args) {15PostfixNotation postfixNotation = new PostfixNotation("123+-");16postfixNotation.calculate();17}1819private void calculate() {20Stack<Integer> numbers = new Stack<>();2122for (char c : this.expression.toCharArray()) {23switch (c) {24case '+':25numbers.push(numbers.pop() + numbers.pop());26break;27case '-':28int right = numbers.pop();29int left = numbers.pop();30numbers.push(left - right);31break;32default:33numbers.push(Integer.parseInt(c + ""));34}35}3637System.out.println(numbers.pop());38}39}
3.3 예시 - 변경 방법1
1// PostfixParser.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;34import java.util.Stack;56public class PostfixParser {78public static PostfixExpression parse(String expression) {9Stack<PostfixExpression> stack = new Stack<>();10for (char c : expression.toCharArray()) {11stack.push(getExpression(c, stack));12}13return stack.pop();14}1516private static PostfixExpression getExpression(17char c,18Stack<PostfixExpression> stack19) {20switch (c) {21case '+':22return new PlusExpression(stack.pop(), stack.pop());23case '-':24PostfixExpression right = stack.pop();25PostfixExpression left = stack.pop();26return new MinusExpression(left, right);27default:28return new VariableExpression(c);29}30}31}
1// PostfixExpression.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;34import java.util.Map;56public interface PostfixExpression {7int interpret(Map<Character, Integer> context);8}
1// VariableExpression.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;34import java.util.Map;56public class VariableExpression implements PostfixExpression {78private Character character;910public VariableExpression(Character character) {11this.character = character;12}1314@Override15public int interpret(Map<Character, Integer> context) {16return context.get(this.character);17}18}
1// PlusExpression.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;34import java.util.Map;56public class PlusExpression implements PostfixExpression {78private PostfixExpression left;910private PostfixExpression right;1112public PlusExpression(PostfixExpression left, PostfixExpression right) {13this.left = left;14this.right = right;15}1617@Override18public int interpret(Map<Character, Integer> context) {19return left.interpret(context) + right.interpret(context);20}21}
1// MinusExpression.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;34import java.util.Map;56public class MinusExpression implements PostfixExpression {78private PostfixExpression left;910private PostfixExpression right;1112public MinusExpression(PostfixExpression left, PostfixExpression right) {13this.left = left;14this.right = right;15}1617@Override18public int interpret(Map<Character, Integer> context) {19return left.interpret(context) - right.interpret(context);20}21}
1// MultiplyExpression.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;34import java.util.Map;56public class MultiplyExpression implements PostfixExpression {78private PostfixExpression left, right;910public MultiplyExpression(PostfixExpression left, PostfixExpression right) {11this.left = left;12this.right = right;13}1415@Override16public int interpret(Map<Character, Integer> context) {17return left.interpret(context) * right.interpret(context);18}19}
1// App.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;34import java.util.Map;56public class App {78public static void main(String[] args) {9PostfixExpression expression = PostfixParser.parse("xyz+-a+");10int result = expression.interpret(Map.of('x', 1, 'y', 2, 'z', 3, 'a', 4));11System.out.println(result);12}13}
3.4 예시 - 변경 방법2
1// PostfixExpression.java2public interface PostfixExpression {3int interpret(Map<Character, Integer> context);45static PostfixExpression plus(6PostfixExpression left,7PostfixExpression right8) {9return context -> left.interpret(context) + right.interpret(context);10}1112static PostfixExpression minus(13PostfixExpression left,14PostfixExpression right15) {16return context -> left.interpret(context) - right.interpret(context);17}1819static PostfixExpression variable(Character c) {20return context -> context.get(c);21}22}
1// PostfixParser.java2public class PostfixParser {34public static PostfixExpression parse(String expression) {5Stack<PostfixExpression> stack = new Stack<>();6for (char c : expression.toCharArray()) {7stack.push(getExpression(c, stack));8}9return stack.pop();10}1112private static PostfixExpression getExpression(13char c,14Stack<PostfixExpression> stack15) {16switch (c) {17case '+':18return PostfixExpression.plus(stack.pop(), stack.pop());19case '-':20PostfixExpression right = stack.pop();21PostfixExpression left = stack.pop();22return PostfixExpression.minus(left, right);23default:24return PostfixExpression.variable(c);25}26}27}
3.5 장단점
- 장점
- 자주 등장하는 문제 패턴을 언어와 문법으로 정의할 수 있다.
- 기존 코드를 변경하지 않고 새로운 Expression을 추가할 수 있다.
- 단점 : 복잡한 문법을 표현하려면 Expression과 Parser가 복잡해진다
3.6 실무에서 어떻게 쓰이나?
자바
- 자바 컴파일러
- 정규 표현식
1// InterpreterInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._03_java;34import java.util.regex.Pattern;56public class InterpreterInJava {78public static void main(String[] args) {9System.out.println(Pattern.matches(".pr...", "spring"));10System.out.println(Pattern.matches("[a-z]{6}", "spring"));11System.out.println(Pattern.matches("white[a-z]{4}[0-9]{4}", "whiteship2000"));12System.out.println(Pattern.matches("\\d", "1")); // one digit13System.out.println(Pattern.matches("\\D", "a")); // one non-digit14}15}
스프링
- SpEL (스프링 Expression Language)
1// InterpreterInSpring.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._03_java;34import org.springframework.expression.Expression;5import org.springframework.expression.ExpressionParser;6import org.springframework.expression.spel.standard.SpelExpressionParser;78public class InterpreterInSpring {910public static void main(String[] args) {11Book book = new Book("spring");1213ExpressionParser parser = new SpelExpressionParser();14Expression expression = parser.parseExpression("title");15System.out.println(expression.getValue(book));16}17}
1// MyService.java2package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._03_java;34import org.springframework.beans.factory.annotation.Value;5import org.springframework.boot.ApplicationArguments;6import org.springframework.boot.ApplicationRunner;7import org.springframework.stereotype.Service;89@Service10public class MyService implements ApplicationRunner {1112@Value("#{2 + 5}")13private String value;1415@Override16public void run(ApplicationArguments args) throws Exception {17System.out.println(value);18}19}
4. 이터레이터(Interator) 패턴
4.1 패턴 소개

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

4.2 예시 - 기존
1// Post.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before;34import java.time.LocalDateTime;56public class Post {78private String title;910private LocalDateTime createdDateTime;1112public Post(String title) {13this.title = title;14this.createdDateTime = LocalDateTime.now();15}1617public String getTitle() {18return title;19}2021public void setTitle(String title) {22this.title = title;23}2425public LocalDateTime getCreatedDateTime() {26return createdDateTime;27}2829public void setCreatedDateTime(LocalDateTime createdDateTime) {30this.createdDateTime = createdDateTime;31}32}
1// Board.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before;34import java.util.ArrayList;5import java.util.List;67public class Board {89List<Post> posts = new ArrayList<>();1011public List<Post> getPosts() {12return posts;13}1415public void setPosts(List<Post> posts) {16this.posts = posts;17}1819public void addPost(String content) {20this.posts.add(new Post(content));21}22}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before;34import java.util.Collections;5import java.util.List;67public class Client {89public static void main(String[] args) {10Board board = new Board();11board.addPost("디자인 패턴 게임");12board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");13board.addPost(14"지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다."15);1617// TODO 들어간 순서대로 순회하기18List<Post> posts = board.getPosts();19for (int i = 0; i < posts.size(); i++) {20Post post = posts.get(i);21System.out.println(post.getTitle());22}2324// TODO 가장 최신 글 먼저 순회하기25Collections.sort(26posts,27(p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime())28);29for (int i = 0; i < posts.size(); i++) {30Post post = posts.get(i);31System.out.println(post.getTitle());32}33}34}
4.3 예시 - 변경
1// RecentPostIterator.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after;34import java.util.Collections;5import java.util.Iterator;6import java.util.List;7import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before.Post;89public class RecentPostIterator implements Iterator<Post> {1011private Iterator<Post> internalIterator;1213public RecentPostIterator(List<Post> posts) {14Collections.sort(15posts,16(p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime())17);18this.internalIterator = posts.iterator();19}2021@Override22public boolean hasNext() {23return this.internalIterator.hasNext();24}2526@Override27public Post next() {28return this.internalIterator.next();29}30}
1// Board.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after;34import java.util.ArrayList;5import java.util.Iterator;6import java.util.List;7import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before.Post;89public class Board {1011List<Post> posts = new ArrayList<>();1213public List<Post> getPosts() {14return posts;15}1617public void addPost(String content) {18this.posts.add(new Post(content));19}2021public Iterator<Post> getRecentPostIterator() {22return new RecentPostIterator(this.posts);23}24}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after;34import java.util.Iterator;5import java.util.List;6import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before.Post;78public class Client {910public static void main(String[] args) {11Board board = new Board();12board.addPost("디자인 패턴 게임");13board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");14board.addPost(15"지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다."16);1718// TODO 들어간 순서대로 순회하기19List<Post> posts = board.getPosts();20Iterator<Post> iterator = posts.iterator();21System.out.println(iterator.getClass());2223for (int i = 0; i < posts.size(); i++) {24Post post = posts.get(i);25System.out.println(post.getTitle());26}2728// TODO 가장 최신 글 먼저 순회하기29Iterator<Post> recentPostIterator = board.getRecentPostIterator();30while (recentPostIterator.hasNext()) {31System.out.println(recentPostIterator.next().getTitle());32}33}34}
4.4 장단점
- 장점
- 집합 객체가 가지고 있는 객체들에 손쉽게 접근할 수 있다.
- 일관된 인터페이스를 사용해 여러 형태의 집합 구조를 순회할 수 있다.
- 단점
- 클래스가 늘어나고 복잡도가 증가한다
4.5 실무에서 어떻게 쓰이나?
자바
- java.util.Enumeration과 java.util.Iterator
- Java StAX (Streaming API for XML)의 Iterator 기반 API
- XmlEventReader, XmlEventWriter
1// IteratorInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._03_java;34import java.io.FileInputStream;5import java.io.FileNotFoundException;6import java.util.Enumeration;7import java.util.Iterator;8import javax.xml.namespace.QName;9import javax.xml.stream.XMLEventReader;10import javax.xml.stream.XMLInputFactory;11import javax.xml.stream.XMLStreamException;12import javax.xml.stream.events.Attribute;13import javax.xml.stream.events.StartElement;14import javax.xml.stream.events.XMLEvent;15import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after.Board;1617public class IteratorInJava {1819public static void main(String[] args)20throws FileNotFoundException, XMLStreamException {21Enumeration enumeration;22Iterator iterator;2324Board board = new Board();25board.addPost("디자인 패턴 게임");26board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");27board.addPost(28"지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다."29);3031// board.getPosts().iterator().forEachRemaining(p -> System.out.println(p.getTitle()));3233// TODO Streaming API for XML(StAX), 이터레이터 기반의 API34XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();35XMLEventReader reader = xmlInputFactory.createXMLEventReader(36new FileInputStream("Book.xml")37);3839while (reader.hasNext()) {40XMLEvent nextEvent = reader.nextEvent();41if (nextEvent.isStartElement()) {42StartElement startElement = nextEvent.asStartElement();43QName name = startElement.getName();44if (name.getLocalPart().equals("book")) {45Attribute title = startElement.getAttributeByName(new QName("title"));46System.out.println(title.getValue());47}48}49}50}51}
스프링
- CompositeIterator
1// IteratorInSpring.java2package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._03_java;34import org.springframework.util.CompositeIterator;56public class IteratorInSpring {78public static void main(String[] args) {9CompositeIterator iterator;10}11}
5. 중재자(Mediator) 패턴
5.1 패턴 소개

여러 객체들이 소통하는 방법을 캡슐화하는 패턴.
- 여러 컴포넌트간의 결합도를 중재자를 통해 낮출 수 있다.

5.2 예시 - 기존
1// Guest.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;34public class Guest {56private Restaurant restaurant = new Restaurant();78private CleaningService cleaningService = new CleaningService();910public void dinner() {11restaurant.dinner(this);12}1314public void getTower(int numberOfTower) {15cleaningService.getTower(this, numberOfTower);16}17}
1// Gym.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;34public class Gym {56private CleaningService cleaningService;78public void clean() {9cleaningService.clean(this);10}11}
1// Restaurant.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;34public class Restaurant {56private CleaningService cleaningService = new CleaningService();78public void dinner(Guest guest) {9System.out.println("dinner " + guest);10}1112public void clean() {13cleaningService.clean(this);14}15}
1// CleaningService.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;34public class CleaningService {56public void clean(Gym gym) {7System.out.println("clean " + gym);8}910public void getTower(Guest guest, int numberOfTower) {11System.out.println(numberOfTower + " towers to " + guest);12}1314public void clean(Restaurant restaurant) {15System.out.println("clean " + restaurant);16}17}
1// Hotel.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;34public class Hotel {56public static void main(String[] args) {7Guest guest = new Guest();8guest.getTower(3);9guest.dinner();1011Restaurant restaurant = new Restaurant();12restaurant.clean();13}14}
5.3 예시 - 변경
1// CleaningService.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;34public class CleaningService {56private FrontDesk frontDesk = new FrontDesk();78public void getTowers(Integer guestId, int numberOfTowers) {9String roomNumber = this.frontDesk.getRoomNumberFor(guestId);10System.out.println("provide " + numberOfTowers + " to " + roomNumber);11}12}
1// Guest.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;34import java.time.LocalDateTime;56public class Guest {78private Integer id;910private FrontDesk frontDesk = new FrontDesk();1112public void getTowers(int numberOfTowers) {13this.frontDesk.getTowers(this, numberOfTowers);14}1516private void dinner(LocalDateTime dateTime) {17this.frontDesk.dinner(this, dateTime);18}1920public Integer getId() {21return id;22}2324public void setId(Integer id) {25this.id = id;26}27}
1// Restaurant.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;34import java.time.LocalDateTime;56public class Restaurant {78public void dinner(Integer id, LocalDateTime dateTime) {}9}
1// FrontDesk.java2package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;34import java.time.LocalDateTime;56public class FrontDesk {78private CleaningService cleaningService = new CleaningService();910private Restaurant restaurant = new Restaurant();1112public void getTowers(Guest guest, int numberOfTowers) {13cleaningService.getTowers(guest.getId(), numberOfTowers);14}1516public String getRoomNumberFor(Integer guestId) {17return "1111";18}1920public void dinner(Guest guest, LocalDateTime dateTime) {21restaurant.dinner(guest.getId(), dateTime);22}23}
5.4 장단점
- 장점
- 컴포넌트 코드를 변경하지 않고 새로운 중재자를 만들어 사용할 수 있다.
- 각각의 컴포넌트 코드를 보다 간결하게 유지할 수 있다.
- 단점
- 중재자 역할을 하는 클래스의 복잡도와 결합도가 증가한다
5.5 실무에서 어떻게 쓰이나?

- 자바
- ExecutorService
- Executor
- 스프링
- DispatcherServlet
6. 메멘토(Mediator) 패턴
6.1 패턴 소개

캡슐화를 유지하면서 객체 내부 상태를 외부에 저장하는 방법.
- 객체 상태를 외부에 저장했다가 해당 상태로 다시 복구할 수 있다.

6.2 예시 - 기존
1// Game.java2package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._01_before;34import java.io.Serializable;56public class Game implements Serializable {78private int redTeamScore;910private int blueTeamScore;1112public int getRedTeamScore() {13return redTeamScore;14}1516public void setRedTeamScore(int redTeamScore) {17this.redTeamScore = redTeamScore;18}1920public int getBlueTeamScore() {21return blueTeamScore;22}2324public void setBlueTeamScore(int blueTeamScore) {25this.blueTeamScore = blueTeamScore;26}27}28
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._01_before;34public class Client {56public static void main(String[] args) {7Game game = new Game();8game.setRedTeamScore(10);9game.setBlueTeamScore(20);1011int blueTeamScore = game.getBlueTeamScore();12int redTeamScore = game.getRedTeamScore();1314Game restoredGame = new Game();15restoredGame.setBlueTeamScore(blueTeamScore);16restoredGame.setRedTeamScore(redTeamScore);17}18}
6.3 예시 - 변경
1// GameSave.java2package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._02_after;34public final class GameSave {56private final int blueTeamScore;78private final int redTeamScore;910public GameSave(int blueTeamScore, int redTeamScore) {11this.blueTeamScore = blueTeamScore;12this.redTeamScore = redTeamScore;13}1415public int getBlueTeamScore() {16return blueTeamScore;17}1819public int getRedTeamScore() {20return redTeamScore;21}22}
1// Game.java2package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._02_after;34public class Game {56private int redTeamScore;78private int blueTeamScore;910public int getRedTeamScore() {11return redTeamScore;12}1314public void setRedTeamScore(int redTeamScore) {15this.redTeamScore = redTeamScore;16}1718public int getBlueTeamScore() {19return blueTeamScore;20}2122public void setBlueTeamScore(int blueTeamScore) {23this.blueTeamScore = blueTeamScore;24}2526public GameSave save() {27return new GameSave(this.blueTeamScore, this.redTeamScore);28}2930public void restore(GameSave gameSave) {31this.blueTeamScore = gameSave.getBlueTeamScore();32this.redTeamScore = gameSave.getRedTeamScore();33}34}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._02_after;34public class Client {56public static void main(String[] args) {7Game game = new Game();8game.setBlueTeamScore(10);9game.setRedTeamScore(20);1011GameSave save = game.save();1213game.setBlueTeamScore(12);14game.setRedTeamScore(22);1516game.restore(save);1718System.out.println(game.getBlueTeamScore());19System.out.println(game.getRedTeamScore());20}21}
6.4 장단점
- 장점
- 캡슐화를 지키면서 상태 객체 상태 스냅샷을 만들 수 있다.
- 객체 상태 저장하고 또는 복원하는 역할을 CareTaker에게 위임할 수 있다.
- 객체 상태가 바뀌어도 클라이언트 코드는 변경되지 않는다.
- 단점
- 많은 정보를 저장하는 Mementor를 자주 생성하는 경우 메모리 사용량에 많은 영향을 줄 수 있 다
6.5 실무에서 어떻게 쓰이나?
자바
- 객체 직렬화, java.io.Serializable
- java.util.Date
1// MementoInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._03_java;34import java.io.*;5import me.whiteship.designpatterns._03_behavioral_patterns._18_memento._01_before.Game;67public class MementoInJava {89public static void main(String[] args)10throws IOException, ClassNotFoundException {11// TODO Serializable12Game game = new Game();13game.setRedTeamScore(10);14game.setBlueTeamScore(20);1516// TODO 직렬화17try (18FileOutputStream fileOut = new FileOutputStream("GameSave.hex");19ObjectOutputStream out = new ObjectOutputStream(fileOut)20) {21out.writeObject(game);22}2324game.setBlueTeamScore(25);25game.setRedTeamScore(15);2627// TODO 역직렬화28try (29FileInputStream fileIn = new FileInputStream("GameSave.hex");30ObjectInputStream in = new ObjectInputStream(fileIn)31) {32game = (Game) in.readObject();33System.out.println(game.getBlueTeamScore());34System.out.println(game.getRedTeamScore());35}36}37}
8. 옵저버(Observer) 패턴
8.1 패턴 소개

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

8.2 예시 - 기존
1// ChatServer.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._01_before;34import java.util.ArrayList;5import java.util.HashMap;6import java.util.List;7import java.util.Map;89public class ChatServer {1011private Map<String, List<String>> messages;1213public ChatServer() {14this.messages = new HashMap<>();15}1617public void add(String subject, String message) {18if (messages.containsKey(subject)) {19messages.get(subject).add(message);20} else {21List<String> messageList = new ArrayList<>();22messageList.add(message);23messages.put(subject, messageList);24}25}2627public List<String> getMessage(String subject) {28return messages.get(subject);29}30}
1// User.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._01_before;34import java.util.List;56public class User {78private ChatServer chatServer;910public User(ChatServer chatServer) {11this.chatServer = chatServer;12}1314public void sendMessage(String subject, String message) {15chatServer.add(subject, message);16}1718public List<String> getMessage(String subject) {19return chatServer.getMessage(subject);20}21}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._01_before;34public class Client {56public static void main(String[] args) {7ChatServer chatServer = new ChatServer();89User user1 = new User(chatServer);10user1.sendMessage("디자인패턴", "이번엔 옵저버 패턴입니다.");11user1.sendMessage("롤드컵2021", "LCK 화이팅!");1213User user2 = new User(chatServer);14System.out.println(user2.getMessage("디자인패턴"));1516user1.sendMessage("디자인패턴", "예제 코드 보는 중..");17System.out.println(user2.getMessage("디자인패턴"));18}19}
8.3 예시 - 변경
1// Subscriber.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;34public interface Subscriber {5void handleMessage(String message);6}
1// User.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;34public class User implements Subscriber {56private String name;78public User(String name) {9this.name = name;10}1112public String getName() {13return name;14}1516@Override17public void handleMessage(String message) {18System.out.println(message);19}20}
1// ChatServer.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;34import java.util.ArrayList;5import java.util.HashMap;6import java.util.List;7import java.util.Map;89public class ChatServer {1011private Map<String, List<Subscriber>> subscribers = new HashMap<>();1213public void register(String subject, Subscriber subscriber) {14if (this.subscribers.containsKey(subject)) {15this.subscribers.get(subject).add(subscriber);16} else {17List<Subscriber> list = new ArrayList<>();18list.add(subscriber);19this.subscribers.put(subject, list);20}21}2223public void unregister(String subject, Subscriber subscriber) {24if (this.subscribers.containsKey(subject)) {25this.subscribers.get(subject).remove(subscriber);26}27}2829public void sendMessage(User user, String subject, String message) {30if (this.subscribers.containsKey(subject)) {31String userMessage = user.getName() + ": " + message;32this.subscribers.get(subject).forEach(s -> s.handleMessage(userMessage));33}34}35}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;34public class Client {56public static void main(String[] args) {7ChatServer chatServer = new ChatServer();8User user1 = new User("keesun");9User user2 = new User("whiteship");1011chatServer.register("오징어게임", user1);12chatServer.register("오징어게임", user2);1314chatServer.register("디자인패턴", user1);1516chatServer.sendMessage(17user1,18"오징어게임",19"아.. 이름이 기억났어.. 일남이야.. 오일남"20);21chatServer.sendMessage(user2, "디자인패턴", "옵저버 패턴으로 만든 채팅");2223chatServer.unregister("디자인패턴", user2);2425chatServer.sendMessage(user2, "디자인패턴", "옵저버 패턴 장, 단점 보는 중");26}27}
8.4 장단점
- 장점
- 상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subsriber)의 관계를 느슨하게 유지할 수 있다.
- Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있다.
- 런타임에 옵저버를 추가하거나 제거할 수 있다.
- 단점
- 복잡도가 증가한다.
- 다수의 Observer 객체를 등록 이후 해지 않는다면 memory leak이 발생할 수도 있다
8.5 실무에서 어떻게 쓰이나?
자바
- Observable과 Observer (자바 9부터 deprecated)
- 자바 9 이후 부터는
- PropertyChangeListener, PropertyChangeEvent
- Flow API
- SAX (Simple API for XML) 라이브러리
1// ObserverInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;34import java.util.Observable;5import java.util.Observer;67public class ObserverInJava {89static class User implements Observer {1011@Override12public void update(Observable o, Object arg) {13System.out.println(arg);14}15}1617static class Subject extends Observable {1819public void add(String message) {20setChanged();21notifyObservers(message);22}23}2425public static void main(String[] args) {26Subject subject = new Subject();27User user = new User();28subject.addObserver(user);29subject.add("Hello Java, Observer");30}31}
1// PropertyChangeExample.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;34import java.beans.PropertyChangeEvent;5import java.beans.PropertyChangeListener;6import java.beans.PropertyChangeSupport;78public class PropertyChangeExample {910static class User implements PropertyChangeListener {1112@Override13public void propertyChange(PropertyChangeEvent evt) {14System.out.println(evt.getNewValue());15}16}1718static class Subject {1920PropertyChangeSupport support = new PropertyChangeSupport(this);2122public void addObserver(PropertyChangeListener observer) {23support.addPropertyChangeListener(observer);24}2526public void removeObserver(PropertyChangeListener observer) {27support.removePropertyChangeListener(observer);28}2930public void add(String message) {31support.firePropertyChange("eventName", null, message);32}33}3435public static void main(String[] args) {36Subject subject = new Subject();37User observer = new User();38subject.addObserver(observer);39subject.add("자바 PCL 예제 코드");40subject.removeObserver(observer);41subject.add("이 메시지는 볼 수 없지..");42}43}
1// FlowInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;34import java.util.concurrent.Flow;5import java.util.concurrent.SubmissionPublisher;67public class FlowInJava {89public static void main(String[] args) throws InterruptedException {10Flow.Publisher<String> publisher = new SubmissionPublisher<>();1112Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() {13private Flow.Subscription subscription;1415@Override16public void onSubscribe(Flow.Subscription subscription) {17System.out.println("sub!");18this.subscription = subscription;19this.subscription.request(1);20}2122@Override23public void onNext(String item) {24System.out.println("onNext called");25System.out.println(Thread.currentThread().getName());26System.out.println(item);27}2829@Override30public void onError(Throwable throwable) {}3132@Override33public void onComplete() {34System.out.println("completed");35}36};3738publisher.subscribe(subscriber);3940((SubmissionPublisher) publisher).submit("hello java");4142System.out.println("이게 먼저 출력될 수도 있습니다.");43}44}
스프링
- ApplicationContext와 ApplicationEvent
1// MyEvent.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;34public class MyEvent {56private String message;78public MyEvent(String message) {9this.message = message;10}1112public String getMessage() {13return message;14}15}
1// MyEventListener.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;34import org.springframework.context.event.EventListener;5import org.springframework.stereotype.Component;67@Component8public class MyEventListener {910@EventListener(MyEvent.class)11public void onApplicationEvent(MyEvent event) {12System.out.println(event.getMessage());13}14}
1// MyRunner.java2package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;34import org.springframework.boot.ApplicationArguments;5import org.springframework.boot.ApplicationRunner;6import org.springframework.context.ApplicationEventPublisher;7import org.springframework.stereotype.Component;89@Component10public class MyRunner implements ApplicationRunner {1112private ApplicationEventPublisher publisher;1314public MyRunner(ApplicationEventPublisher publisher) {15this.publisher = publisher;16}1718@Override19public void run(ApplicationArguments args) throws Exception {20publisher.publishEvent(new MyEvent("hello spring event"));21}22}
9. 상태(State) 패턴
9.1 패턴 소개

객체 내부 상태 변경에 따라 객체의 행동이 달라지는 패턴.
- 상태에 특화된 행동들을 분리해 낼 수 있으며, 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.

9.2 예시 - 기존
1// Student.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._01_before;34import java.util.ArrayList;5import java.util.List;67public class Student {89private String name;1011public Student(String name) {12this.name = name;13}1415private List<OnlineCourse> privateCourses = new ArrayList<>();1617public boolean isEnabledForPrivateClass(OnlineCourse onlineCourse) {18return privateCourses.contains(onlineCourse);19}2021public void addPrivateCourse(OnlineCourse onlineCourse) {22this.privateCourses.add(onlineCourse);23}2425@Override26public String toString() {27return "Student{" + "name='" + name + '\'' + '}';28}29}
1// OnlineCourse.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._01_before;34import java.util.ArrayList;5import java.util.List;67public class OnlineCourse {89public enum State {10DRAFT,11PUBLISHED,12PRIVATE,13}1415private State state = State.DRAFT;1617private List<String> reviews = new ArrayList<>();1819private List<Student> students = new ArrayList<>();2021public void addReview(String review, Student student) {22if (this.state == State.PUBLISHED) {23this.reviews.add(review);24} else if (this.state == State.PRIVATE && this.students.contains(student)) {25this.reviews.add(review);26} else {27throw new UnsupportedOperationException("리뷰를 작성할 수 없습니다.");28}29}3031public void addStudent(Student student) {32if (this.state == State.DRAFT || this.state == State.PUBLISHED) {33this.students.add(student);34} else if (this.state == State.PRIVATE && availableTo(student)) {35this.students.add(student);36} else {37throw new UnsupportedOperationException(38"학생을 해당 수업에 추가할 수 없습니다."39);40}4142if (this.students.size() > 1) {43this.state = State.PRIVATE;44}45}4647public void changeState(State newState) {48this.state = newState;49}5051public State getState() {52return state;53}5455public List<String> getReviews() {56return reviews;57}5859public List<Student> getStudents() {60return students;61}6263private boolean availableTo(Student student) {64return student.isEnabledForPrivateClass(this);65}66}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._01_before;34public class Client {56public static void main(String[] args) {7Student student = new Student("whiteship");8OnlineCourse onlineCourse = new OnlineCourse();910Student keesun = new Student("keesun");11keesun.addPrivateCourse(onlineCourse);1213onlineCourse.addStudent(student);14onlineCourse.changeState(OnlineCourse.State.PRIVATE);1516onlineCourse.addStudent(keesun);1718onlineCourse.addReview("hello", student);1920System.out.println(onlineCourse.getState());21System.out.println(onlineCourse.getStudents());22System.out.println(onlineCourse.getReviews());23}24}
9.3 예시 - 변경
1// State.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;34public interface State {5void addReview(String review, Student student);67void addStudent(Student student);8}
1// Draft.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;34public class Draft implements State {56private OnlineCourse onlineCourse;78public Draft(OnlineCourse onlineCourse) {9this.onlineCourse = onlineCourse;10}1112@Override13public void addReview(String review, Student student) {14throw new UnsupportedOperationException(15"드래프트 상태에서는 리뷰를 남길 수 없습니다."16);17}1819@Override20public void addStudent(Student student) {21this.onlineCourse.getStudents().add(student);22if (this.onlineCourse.getStudents().size() > 1) {23this.onlineCourse.changeState(new Private(this.onlineCourse));24}25}26}
1// Private.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;34public class Private implements State {56private OnlineCourse onlineCourse;78public Private(OnlineCourse onlineCourse) {9this.onlineCourse = onlineCourse;10}1112@Override13public void addReview(String review, Student student) {14if (this.onlineCourse.getStudents().contains(student)) {15this.onlineCourse.getReviews().add(review);16} else {17throw new UnsupportedOperationException(18"프라이빗 코스를 수강하는 학생만 리뷰를 남길 수 있습니다."19);20}21}2223@Override24public void addStudent(Student student) {25if (student.isAvailable(this.onlineCourse)) {26this.onlineCourse.getStudents().add(student);27} else {28throw new UnsupportedOperationException(29"프라이빛 코스를 수강할 수 없습니다."30);31}32}33}
1// Published.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;34public class Published implements State {56private OnlineCourse onlineCourse;78public Published(OnlineCourse onlineCourse) {9this.onlineCourse = onlineCourse;10}1112@Override13public void addReview(String review, Student student) {14this.onlineCourse.getReviews().add(review);15}1617@Override18public void addStudent(Student student) {19this.onlineCourse.getStudents().add(student);20}21}
1// OnlineCourse.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;34import java.util.ArrayList;5import java.util.List;67public class OnlineCourse {89private State state = new Draft(this);1011private List<Student> students = new ArrayList<>();1213private List<String> reviews = new ArrayList<>();1415public void addStudent(Student student) {16this.state.addStudent(student);17}1819public void addReview(String review, Student student) {20this.state.addReview(review, student);21}2223public State getState() {24return state;25}2627public List<Student> getStudents() {28return students;29}3031public List<String> getReviews() {32return reviews;33}3435public void changeState(State state) {36this.state = state;37}38}
1// Student.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;34import java.util.HashSet;5import java.util.Set;67public class Student {89private String name;1011public Student(String name) {12this.name = name;13}1415private Set<OnlineCourse> onlineCourses = new HashSet<>();1617public boolean isAvailable(OnlineCourse onlineCourse) {18return onlineCourses.contains(onlineCourse);19}2021public void addPrivate(OnlineCourse onlineCourse) {22this.onlineCourses.add(onlineCourse);23}2425@Override26public String toString() {27return "Student{" + "name='" + name + '\'' + '}';28}29}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;34public class Client {56public static void main(String[] args) {7OnlineCourse onlineCourse = new OnlineCourse();8Student student = new Student("whiteship");9Student keesun = new Student("keesun");10keesun.addPrivate(onlineCourse);1112onlineCourse.addStudent(student);1314onlineCourse.changeState(new Private(onlineCourse));1516onlineCourse.addReview("hello", student);1718onlineCourse.addStudent(keesun);1920System.out.println(onlineCourse.getState());21System.out.println(onlineCourse.getReviews());22System.out.println(onlineCourse.getStudents());23}24}
9.4 장단점
- 장점
- 상태에 따른 동작을 개별 클래스로 옮겨서 관리할 수 있다.
- 기존의 특정 상태에 따른 동작을 변경하지 않고 새로운 상태에 다른 동작을 추가할 수 있다.
- 코드 복잡도를 줄일 수 있다.
- 단점
- 복잡도가 증가한다
10. 전략(Strategy) 패턴
10.1 패턴 소개

여러 알고리즘을 캡슐화하고 상호 교환 가능하게 만드는 패턴.
- 컨텍스트에서 사용할 알고리듬을 클라이언트 선택한다.

10.2 예시 - 기존
1// BlueLightRedLight.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._01_before;34public class BlueLightRedLight {56private int speed;78public BlueLightRedLight(int speed) {9this.speed = speed;10}1112public void blueLight() {13if (speed == 1) {14System.out.println("무 궁 화 꽃 이");15} else if (speed == 2) {16System.out.println("무궁화꽃이");17} else {18System.out.println("무광꼬치");19}20}2122public void redLight() {23if (speed == 1) {24System.out.println("피 었 습 니 다.");25} else if (speed == 2) {26System.out.println("피었습니다.");27} else {28System.out.println("피어씀다");29}30}31}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._01_before;34public class Client {56public static void main(String[] args) {7BlueLightRedLight blueLightRedLight = new BlueLightRedLight(3);8blueLightRedLight.blueLight();9blueLightRedLight.redLight();10}11}
10.3 예시 - 변경
1// Speed.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;34public interface Speed {5void blueLight();67void redLight();8}
1// Normal.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;34public class Normal implements Speed {56@Override7public void blueLight() {8System.out.println("무 궁 화 꽃 이");9}1011@Override12public void redLight() {13System.out.println("피 었 습 니 다.");14}15}
1// Faster.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;34public class Faster implements Speed {56@Override7public void blueLight() {8System.out.println("무궁화꽃이");9}1011@Override12public void redLight() {13System.out.println("피었습니다.");14}15}
1// Fastest.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;34public class Fastest implements Speed {56@Override7public void blueLight() {8System.out.println("무광꼬치");9}1011@Override12public void redLight() {13System.out.println("피어씀다.");14}15}
1// BlueLightRedLight.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;34public class BlueLightRedLight {56public void blueLight(Speed speed) {7speed.blueLight();8}910public void redLight(Speed speed) {11speed.redLight();12}13}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;34public class Client {56public static void main(String[] args) {7BlueLightRedLight game = new BlueLightRedLight();8game.blueLight(new Normal());9game.redLight(new Fastest());10game.blueLight(11new Speed() {12@Override13public void blueLight() {14System.out.println("blue light");15}1617@Override18public void redLight() {19System.out.println("red light");20}21}22);23}24}
10.4 장단점
- 장점
- 새로운 전략을 추가하더라도 기존 코드를 변경하지 않는다.
- 상속 대신 위임을 사용할 수 있다.
- 런타임에 전략을 변경할 수 있다.
- 단점
- 복잡도가 증가한다.
- 클라이언트 코드가 구체적인 전략을 알아야 한다
10.5 실무에서 어떻게 쓰이나?
자바의 Comparator
1package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._03_java;23import java.util.ArrayList;4import java.util.Collections;5import java.util.Comparator;6import java.util.List;78public class StrategyInJava {910public static void main(String[] args) {11List<Integer> numbers = new ArrayList<>();12numbers.add(10);13numbers.add(5);1415System.out.println(numbers);1617Collections.sort(numbers, Comparator.naturalOrder());1819System.out.println(numbers);20}21}
스프링
- ApplicationContext
- PlatformTransactionManager
- …
1// StrategyInSpring.java2package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._03_java;34import org.springframework.beans.factory.xml.BeanDefinitionParser;5import org.springframework.cache.CacheManager;6import org.springframework.context.ApplicationContext;7import org.springframework.context.annotation.AnnotationConfigApplicationContext;8import org.springframework.context.support.ClassPathXmlApplicationContext;9import org.springframework.context.support.FileSystemXmlApplicationContext;10import org.springframework.transaction.PlatformTransactionManager;1112public class StrategyInSpring {1314public static void main(String[] args) {15ApplicationContext applicationContext = new ClassPathXmlApplicationContext();16ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext();17ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext();1819BeanDefinitionParser parser;2021PlatformTransactionManager platformTransactionManager;2223CacheManager cacheManager;24}25}
11. 템플릿 메소드(Template method) 패턴
11.1 패턴 소개

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

템플릿 콜백 (Template-Callback)
- 패턴 콜백으로 상속 대신 위임을 사용하는 템플릿 패턴.
- 상속 대신 익명 내부 클래스 또는 람다 표현식을 활용할 수 있다

11.2 예시 - 기존
1// FileProcessor.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._01_before;34import java.io.BufferedReader;5import java.io.FileReader;6import java.io.IOException;78public class FileProcessor {910private String path;1112public FileProcessor(String path) {13this.path = path;14}1516public int process() {17try (BufferedReader reader = new BufferedReader(new FileReader(path))) {18int result = 0;19String line = null;20while ((line = reader.readLine()) != null) {21result += Integer.parseInt(line);22}23return result;24} catch (IOException e) {25throw new IllegalArgumentException(26path + "에 해당하는 파일이 없습니다.",27e28);29}30}31}
1// MultuplyFileProcessor.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._01_before;34import java.io.BufferedReader;5import java.io.FileReader;6import java.io.IOException;78public class MultuplyFileProcessor {910private String path;1112public MultuplyFileProcessor(String path) {13this.path = path;14}1516public int process() {17try (BufferedReader reader = new BufferedReader(new FileReader(path))) {18int result = 0;19String line = null;20while ((line = reader.readLine()) != null) {21result *= Integer.parseInt(line);22}23return result;24} catch (IOException e) {25throw new IllegalArgumentException(26path + "에 해당하는 파일이 없습니다.",27e28);29}30}31}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._01_before;34public class Client {56public static void main(String[] args) {7FileProcessor fileProcessor = new FileProcessor("number.txt");8int result = fileProcessor.process();9System.out.println(result);10}11}
11.3 예시 - 변경
1// FileProcessor.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;34import java.io.BufferedReader;5import java.io.FileReader;6import java.io.IOException;78public abstract class FileProcessor {910private String path;1112public FileProcessor(String path) {13this.path = path;14}1516public final int process(Operator operator) {17try (BufferedReader reader = new BufferedReader(new FileReader(path))) {18int result = 0;19String line = null;20while ((line = reader.readLine()) != null) {21result = getResult(result, Integer.parseInt(line));22}23return result;24} catch (IOException e) {25throw new IllegalArgumentException(26path + "에 해당하는 파일이 없습니다.",27e28);29}30}3132protected abstract int getResult(int result, int number);33}
1// Multiply.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;34public class Multiply extends FileProcessor {56public Multiply(String path) {7super(path);8}910@Override11protected int getResult(int result, int number) {12return result *= number;13}14}
1// Operator.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;34public interface Operator {5abstract int getResult(int result, int number);6}
1// FileProcessor.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;34import java.io.BufferedReader;5import java.io.FileReader;6import java.io.IOException;78public abstract class FileProcessor {910private String path;1112public FileProcessor(String path) {13this.path = path;14}1516public final int process(Operator operator) {17try (BufferedReader reader = new BufferedReader(new FileReader(path))) {18int result = 0;19String line = null;20while ((line = reader.readLine()) != null) {21result = getResult(result, Integer.parseInt(line));22}23return result;24} catch (IOException e) {25throw new IllegalArgumentException(26path + "에 해당하는 파일이 없습니다.",27e28);29}30}3132protected abstract int getResult(int result, int number);33}
1// Plus.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;34public class Plus implements Operator {56@Override7public int getResult(int result, int number) {8return result += number;9}10}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;34public class Client {56public static void main(String[] args) {7FileProcessor fileProcessor = new Multiply("number.txt");8int result = fileProcessor.process((sum, number) -> sum += number);9System.out.println(result);10}11}
11.4 장단점
- 장점
- 템플릿 코드를 재사용하고 중복 코드를 줄일 수 있다.
- 템플릿 코드를 변경하지 않고 상속을 받아서 구체적인 알고리듬만 변경할 수 있다.
- 단점
- 리스코프 치환 원칙을 위반할 수도 있다.
- 알고리듬 구조가 복잡할 수록 템플릿을 유지하기 어려워진다
11.5 실무에서 어떻게 쓰이나?
자바의 HttpServlet
1// MyHello.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._03_java;34import java.io.IOException;5import javax.servlet.ServletException;6import javax.servlet.http.HttpServlet;7import javax.servlet.http.HttpServletRequest;8import javax.servlet.http.HttpServletResponse;910public class MyHello extends HttpServlet {1112@Override13protected void doGet(HttpServletRequest req, HttpServletResponse resp)14throws ServletException, IOException {15super.doGet(req, resp);16}1718@Override19protected void doPost(HttpServletRequest req, HttpServletResponse resp)20throws ServletException, IOException {21super.doPost(req, resp);22}23}
스프링
- 템플릿 메소드 패턴
- Configuration
- 템플릿 콜백 패턴
- JdbcTemplate
- RestTemplate
- …
1// TemplateInSpring.java2package me.whiteship.designpatterns._03_behavioral_patterns._22_template._03_java;34import java.util.Arrays;5import org.springframework.context.annotation.Configuration;6import org.springframework.http.*;7import org.springframework.jdbc.core.JdbcTemplate;8import org.springframework.security.config.annotation.web.builders.HttpSecurity;9import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;10import org.springframework.web.client.RestTemplate;1112public class TemplateInSpring {1314public static void main(String[] args) {15// TODO 템플릿-콜백 패턴16// JdbcTemplate17JdbcTemplate jdbcTemplate = new JdbcTemplate();18jdbcTemplate.execute("insert");1920// RestTemplate21RestTemplate restTemplate = new RestTemplate();2223HttpHeaders headers = new HttpHeaders();24headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));25headers.set("X-COM-PERSIST", "NO");26headers.set("X-COM-LOCATION", "USA");2728HttpEntity<String> entity = new HttpEntity<String>(headers);29ResponseEntity<String> responseEntity = restTemplate.exchange(30"http://localhost:8080/users",31HttpMethod.GET,32entity,33String.class34);35}3637@Configuration38class SecurityConfig extends WebSecurityConfigurerAdapter {3940@Override41protected void configure(HttpSecurity http) throws Exception {42http.authorizeRequests().anyRequest().permitAll();43}44}45}
12. 방문자(Visitor) 패턴
12.1 패턴 소개

기존 코드를 변경하지 않고 새로운 기능을 추가하는 방법.
- 더블 디스패치 (Double Dispatch)를 활용할 수 있다.
12.2 예시 - 기존
1// Device.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;34public interface Device {}
1// Phone.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;34public class Phone implements Device {}
1// Watch.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;34public class Watch implements Device {}
1// Shape.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;34public interface Shape {5void printTo(Device device);6}
1// Triangle.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;34public class Triangle implements Shape {56@Override7public void printTo(Device device) {8if (device instanceof Phone) {9System.out.println("print Triangle to Phone");10} else if (device instanceof Watch) {11System.out.println("print Triangle to Watch");12}13}14}
1// Circle.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;34public class Circle implements Shape {56@Override7public void printTo(Device device) {8if (device instanceof Phone) {9System.out.println("print Circle to phone");10} else if (device instanceof Watch) {11System.out.println("print Circle to watch");12}13}14}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;34public class Client {56public static void main(String[] args) {7Shape rectangle = new Rectangle();8Device device = new Phone();9rectangle.printTo(device);10}11}
12.3 예시 - 변경
1// Device.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public interface Device {5void print(Circle circle);67void print(Rectangle rectangle);89void print(Triangle triangle);10}
1// Pad.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public class Pad implements Device {56@Override7public void print(Circle circle) {8System.out.println("Print Circle to Pad");9}1011@Override12public void print(Rectangle rectangle) {13System.out.println("Print Rectangle to Pad");14}1516@Override17public void print(Triangle triangle) {18System.out.println("Print Triangle to Pad");19}20}
1// Phone.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public class Phone implements Device {56@Override7public void print(Circle circle) {8System.out.println("Print Circle to Phone");9}1011@Override12public void print(Rectangle rectangle) {13System.out.println("Print Rectangle to Phone");14}1516@Override17public void print(Triangle triangle) {18System.out.println("Print Triangle to Phone");19}20}
1// Watch.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public class Watch implements Device {56@Override7public void print(Circle circle) {8System.out.println("Print Circle to Watch");9}1011@Override12public void print(Rectangle rectangle) {13System.out.println("Print Rectangle to Watch");14}1516@Override17public void print(Triangle triangle) {18System.out.println("Print Triangle to Watch");19}20}
1// Shape.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public interface Shape {5void accept(Device device);6}
1// Circle.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public class Circle implements Shape {56@Override7public void accept(Device device) {8device.print(this);9}10}
1// Triangle.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public class Triangle implements Shape {56@Override7public void accept(Device device) {8device.print(this);9}10}
1// Rectangle.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public class Rectangle implements Shape {56@Override7public void accept(Device device) {8device.print(this);9}10}
1// Client.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;34public class Client {56public static void main(String[] args) {7Shape rectangle = new Rectangle();8Device device = new Pad();9rectangle.accept(device);10}11}
12.4 장단점
- 장점
- 기존 코드를 변경하지 않고 새로운 코드를 추가할 수 있다.
- 추가 기능을 한 곳에 모아둘 수 있다.
- 단점
- 복잡하다.
- 새로운 Element를 추가하거나 제거할 때 모든 Visitor 코드를 변경해야 한다
12.5 실무에서 어떻게 쓰이나?
자바
- FileVisitor, SimpleFileVisitor
- AnnotationValueVisitor
- ElementVisitor
1// SearchFileVisitor.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._03_java;34import java.io.IOException;5import java.nio.file.*;6import java.nio.file.attribute.BasicFileAttributes;78public class SearchFileVisitor implements FileVisitor<Path> {910private String fileToSearch;11private Path startingDirectory;1213public SearchFileVisitor(String fileToSearch, Path startingDirectory) {14this.fileToSearch = fileToSearch;15this.startingDirectory = startingDirectory;16}1718@Override19public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)20throws IOException {21return FileVisitResult.CONTINUE;22}2324@Override25public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)26throws IOException {27if (fileToSearch.equals(file.getFileName().toString())) {28System.out.println("found " + file.getFileName());29return FileVisitResult.TERMINATE;30}31return FileVisitResult.CONTINUE;32}3334@Override35public FileVisitResult visitFileFailed(Path file, IOException exc)36throws IOException {37exc.printStackTrace(System.out);38return FileVisitResult.CONTINUE;39}4041@Override42public FileVisitResult postVisitDirectory(Path dir, IOException exc)43throws IOException {44if (Files.isSameFile(startingDirectory, dir)) {45System.out.println("search end");46return FileVisitResult.TERMINATE;47}48return FileVisitResult.CONTINUE;49}50}
1// VisitorInJava.java2package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._03_java;34import java.io.IOException;5import java.nio.file.Files;6import java.nio.file.Path;78public class VisitorInJava {910public static void main(String[] args) throws IOException {11Path startingDirectory = Path.of("/Users/keesun/workspace/design-patterns");12SearchFileVisitor searchFileVisitor = new SearchFileVisitor(13"Triangle.java",14startingDirectory15);16Files.walkFileTree(startingDirectory, searchFileVisitor);17}18}
스프링의 BeanDefinitionVisitor
1package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._03_java;23import org.springframework.beans.factory.config.BeanDefinitionVisitor;45public class VisitorInSpring {67public static void main(String[] args) {8BeanDefinitionVisitor beanDefinitionVisitor;9}10}