🎉 berenickt 블로그에 온 걸 환영합니다. 🎉
CS
GOF 디자인패턴-JAVA
03-행동 패턴

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

1.1 패턴 소개

gof_2_15

요청을 보내는 쪽(sender)요청을 처리하는 쪽(receiver)의 분리하는 패턴

  • 핸들러 체인을 사용해서 요청을 처리한다

gof_2_16


1.2 예시 - 기존

1
// Request.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;
3
4
public class Request {
5
6
private String body;
7
8
public Request(String body) {
9
this.body = body;
10
}
11
12
public String getBody() {
13
return body;
14
}
15
16
public void setBody(String body) {
17
this.body = body;
18
}
19
}
1
// RequestHandler.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;
3
4
public class RequestHandler {
5
6
public void handler(Request request) {
7
System.out.println(request.getBody());
8
}
9
}
1
// AuthRequestHandler.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;
3
4
public class AuthRequestHandler extends RequestHandler {
5
6
public void handler(Request request) {
7
System.out.println("인증이 되었나?");
8
System.out.println("이 핸들러를 사용할 수 있는 유저인가?");
9
super.handler(request);
10
}
11
}
1
// LoggingRequestHandler.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;
3
4
public class LoggingRequestHandler extends RequestHandler {
5
6
@Override
7
public void handler(Request request) {
8
System.out.println("로깅");
9
super.handler(request);
10
}
11
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
Request request = new Request("무궁화 꽃이 피었습니다.");
8
RequestHandler requestHandler = new LoggingRequestHandler();
9
requestHandler.handler(request);
10
}
11
}

1.3 예시 - 변경

1
// RequestHandler.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;
5
6
public abstract class RequestHandler {
7
8
private RequestHandler nextHandler;
9
10
public RequestHandler(RequestHandler nextHandler) {
11
this.nextHandler = nextHandler;
12
}
13
14
public void handle(Request request) {
15
if (nextHandler != null) {
16
nextHandler.handle(request);
17
}
18
}
19
}
1
// PrintRequestHandler.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;
5
6
public class PrintRequestHandler extends RequestHandler {
7
8
public PrintRequestHandler(RequestHandler nextHandler) {
9
super(nextHandler);
10
}
11
12
@Override
13
public void handle(Request request) {
14
System.out.println(request.getBody());
15
super.handle(request);
16
}
17
}
1
// LoggingRequestHandler.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;
5
6
public class LoggingRequestHandler extends RequestHandler {
7
8
public LoggingRequestHandler(RequestHandler nextHandler) {
9
super(nextHandler);
10
}
11
12
@Override
13
public void handle(Request request) {
14
System.out.println("로깅");
15
super.handle(request);
16
}
17
}
1
// AuthRequestHandler.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;
5
6
public class AuthRequestHandler extends RequestHandler {
7
8
public AuthRequestHandler(RequestHandler nextHandler) {
9
super(nextHandler);
10
}
11
12
@Override
13
public void handle(Request request) {
14
System.out.println("인증이 되었는가?");
15
super.handle(request);
16
}
17
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._01_before.Request;
5
6
public class Client {
7
8
private RequestHandler requestHandler;
9
10
public Client(RequestHandler requestHandler) {
11
this.requestHandler = requestHandler;
12
}
13
14
public void doWork() {
15
Request request = new Request("이번 놀이는 뽑기입니다.");
16
requestHandler.handle(request);
17
}
18
19
public static void main(String[] args) {
20
RequestHandler chain = new AuthRequestHandler(
21
new LoggingRequestHandler(new PrintRequestHandler(null))
22
);
23
Client client = new Client(chain);
24
client.doWork();
25
}
26
}

1.4 장단점

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

1.5 실무에서 어떻게 쓰이나?

  • 자바 : 서블릿 필터
1
// CoRInJava.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;
3
4
import java.io.IOException;
5
import javax.servlet.*;
6
7
public class CoRInJava {
8
9
public static void main(String[] args) {
10
Filter filter = new Filter() {
11
@Override
12
public void doFilter(
13
ServletRequest request,
14
ServletResponse response,
15
FilterChain chain
16
) throws IOException, ServletException {
17
// TODO 전처리
18
chain.doFilter(request, response);
19
// TODO 후처리
20
}
21
};
22
}
23
}
1
// MyFilter.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;
3
4
import java.io.IOException;
5
import javax.servlet.*;
6
import javax.servlet.annotation.WebFilter;
7
8
@WebFilter(urlPatterns = "/hello")
9
public class MyFilter implements Filter {
10
11
@Override
12
public void doFilter(
13
ServletRequest request,
14
ServletResponse response,
15
FilterChain chain
16
) throws IOException, ServletException {
17
System.out.println("게임에 참하신 여러분 모두 진심으로 환영합니다.");
18
chain.doFilter(request, response);
19
System.out.println("꽝!");
20
}
21
}
1
// App.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;
3
4
import org.springframework.boot.SpringApplication;
5
import org.springframework.boot.autoconfigure.SpringBootApplication;
6
import org.springframework.boot.web.servlet.ServletComponentScan;
7
8
@ServletComponentScan
9
@SpringBootApplication
10
public class App {
11
12
public static void main(String[] args) {
13
SpringApplication.run(App.class, args);
14
}
15
}
1
// HelloController.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;
3
4
import org.springframework.web.bind.annotation.GetMapping;
5
import org.springframework.web.bind.annotation.RestController;
6
7
@RestController
8
public class HelloController {
9
10
@GetMapping("/hello")
11
public String hello() {
12
return "hello";
13
}
14
}

gof_3_3

  • 스프링 : 스프링 시큐리티 필터
1
// SecurityConfig.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._13_chain_of_responsibilities._03_java;
3
4
import org.springframework.context.annotation.Configuration;
5
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
7
8
@Configuration
9
public class SecurityConfig extends WebSecurityConfigurerAdapter {
10
11
@Override
12
protected void configure(HttpSecurity http) throws Exception {
13
http.authorizeRequests().anyRequest().permitAll().and();
14
}
15
}

2. 커맨드(Command) 패턴

2.1 패턴 소개

gof_3_4

요청을 캡슐화 하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴.

  • 요청을 처리하는 방법이 바뀌더라도, 호출자의 코드는 변경되지 않는다

gof_3_5


2.2 예시 - 기존

1
// Light.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;
3
4
public class Light {
5
6
private boolean isOn;
7
8
public void on() {
9
System.out.println("불을 켭니다.");
10
this.isOn = true;
11
}
12
13
public void off() {
14
System.out.println("불을 끕니다.");
15
this.isOn = false;
16
}
17
18
public boolean isOn() {
19
return this.isOn;
20
}
21
}
1
// Game.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;
3
4
public class Game {
5
6
private boolean isStarted;
7
8
public void start() {
9
System.out.println("게임을 시작합니다.");
10
this.isStarted = true;
11
}
12
13
public void end() {
14
System.out.println("게임을 종료합니다.");
15
this.isStarted = false;
16
}
17
18
public boolean isStarted() {
19
return isStarted;
20
}
21
}
1
// Button.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;
3
4
public class Button {
5
6
private Light light;
7
8
public Button(Light light) {
9
this.light = light;
10
}
11
12
public void press() {
13
light.off();
14
}
15
16
public static void main(String[] args) {
17
Button button = new Button(new Light());
18
button.press();
19
button.press();
20
button.press();
21
button.press();
22
}
23
}
1
// MyApp.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before;
3
4
public class MyApp {
5
6
private Game game;
7
8
public MyApp(Game game) {
9
this.game = game;
10
}
11
12
public void press() {
13
game.start();
14
}
15
16
public static void main(String[] args) {
17
Button button = new Button(new Light());
18
button.press();
19
button.press();
20
button.press();
21
button.press();
22
}
23
}

2.3 예시 - 변경

1
// Command.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;
3
4
public interface Command {
5
void execute();
6
7
void undo();
8
}
1
// GameEndCommand.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;
5
6
public class GameEndCommand implements Command {
7
8
private Game game;
9
10
public GameEndCommand(Game game) {
11
this.game = game;
12
}
13
14
@Override
15
public void execute() {
16
game.end();
17
}
18
19
@Override
20
public void undo() {
21
new GameStartCommand(this.game).execute();
22
}
23
}
1
// GameStartCommand.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;
5
6
public class GameStartCommand implements Command {
7
8
private Game game;
9
10
public GameStartCommand(Game game) {
11
this.game = game;
12
}
13
14
@Override
15
public void execute() {
16
game.start();
17
}
18
19
@Override
20
public void undo() {
21
new GameEndCommand(this.game).execute();
22
}
23
}
1
// LightOffCommand.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;
5
6
public class LightOffCommand implements Command {
7
8
private Light light;
9
10
public LightOffCommand(Light light) {
11
this.light = light;
12
}
13
14
@Override
15
public void execute() {
16
light.off();
17
}
18
19
@Override
20
public void undo() {
21
new LightOnCommand(this.light).execute();
22
}
23
}
1
// LightOnCommand.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;
5
6
public class LightOnCommand implements Command {
7
8
private Light light;
9
10
public LightOnCommand(Light light) {
11
this.light = light;
12
}
13
14
@Override
15
public void execute() {
16
light.on();
17
}
18
19
@Override
20
public void undo() {
21
new LightOffCommand(this.light).execute();
22
}
23
}
1
// Button.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;
3
4
import java.util.Stack;
5
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;
6
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;
7
8
public class Button {
9
10
private Stack<Command> commands = new Stack<>();
11
12
public void press(Command command) {
13
command.execute();
14
commands.push(command);
15
}
16
17
public void undo() {
18
if (!commands.isEmpty()) {
19
Command command = commands.pop();
20
command.undo();
21
}
22
}
23
24
public static void main(String[] args) {
25
Button button = new Button();
26
button.press(new GameStartCommand(new Game()));
27
button.press(new LightOnCommand(new Light()));
28
button.undo();
29
button.undo();
30
}
31
}
1
// MyApp.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after;
3
4
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;
5
6
public class MyApp {
7
8
private Command command;
9
10
public MyApp(Command command) {
11
this.command = command;
12
}
13
14
public void press() {
15
command.execute();
16
}
17
18
public static void main(String[] args) {
19
MyApp myApp = new MyApp(new GameStartCommand(new Game()));
20
}
21
}

2.4 장단점

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

2.5 실무에서 어떻게 쓰이나?

자바

  • Runnable
  • 람다
  • 메소드 레퍼런스
1
// CommandInJava.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._03_java;
3
4
import java.util.concurrent.ExecutorService;
5
import java.util.concurrent.Executors;
6
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Game;
7
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._01_before.Light;
8
9
public class CommandInJava {
10
11
public static void main(String[] args) {
12
Light light = new Light();
13
Game game = new Game();
14
ExecutorService executorService = Executors.newFixedThreadPool(4);
15
executorService.submit(light::on);
16
executorService.submit(game::start);
17
executorService.submit(game::end);
18
executorService.submit(light::off);
19
executorService.shutdown();
20
}
21
}

스프링

  • SimpleJdbcInsert
  • SimpleJdbcCall
1
// CommandInSpring.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._14_command._03_java;
3
4
import java.time.LocalDateTime;
5
import java.util.HashMap;
6
import java.util.Map;
7
import javax.sql.DataSource;
8
import me.whiteship.designpatterns._03_behavioral_patterns._14_command._02_after.Command;
9
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
10
11
public class CommandInSpring {
12
13
private DataSource dataSource;
14
15
public CommandInSpring(DataSource dataSource) {
16
this.dataSource = dataSource;
17
}
18
19
public void add(Command command) {
20
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource)
21
.withTableName("command")
22
.usingGeneratedKeyColumns("id");
23
24
Map<String, Object> data = new HashMap<>();
25
data.put("name", command.getClass().getSimpleName());
26
data.put("when", LocalDateTime.now());
27
insert.execute(data);
28
}
29
}

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

3.1 패턴 소개

gof_3_6

자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴.

  • 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있다

gof_3_7


3.2 예시 - 기존

1
// PostfixNotation.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._01_before;
3
4
import java.util.Stack;
5
6
public class PostfixNotation {
7
8
private final String expression;
9
10
public PostfixNotation(String expression) {
11
this.expression = expression;
12
}
13
14
public static void main(String[] args) {
15
PostfixNotation postfixNotation = new PostfixNotation("123+-");
16
postfixNotation.calculate();
17
}
18
19
private void calculate() {
20
Stack<Integer> numbers = new Stack<>();
21
22
for (char c : this.expression.toCharArray()) {
23
switch (c) {
24
case '+':
25
numbers.push(numbers.pop() + numbers.pop());
26
break;
27
case '-':
28
int right = numbers.pop();
29
int left = numbers.pop();
30
numbers.push(left - right);
31
break;
32
default:
33
numbers.push(Integer.parseInt(c + ""));
34
}
35
}
36
37
System.out.println(numbers.pop());
38
}
39
}

3.3 예시 - 변경 방법1

1
// PostfixParser.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;
3
4
import java.util.Stack;
5
6
public class PostfixParser {
7
8
public static PostfixExpression parse(String expression) {
9
Stack<PostfixExpression> stack = new Stack<>();
10
for (char c : expression.toCharArray()) {
11
stack.push(getExpression(c, stack));
12
}
13
return stack.pop();
14
}
15
16
private static PostfixExpression getExpression(
17
char c,
18
Stack<PostfixExpression> stack
19
) {
20
switch (c) {
21
case '+':
22
return new PlusExpression(stack.pop(), stack.pop());
23
case '-':
24
PostfixExpression right = stack.pop();
25
PostfixExpression left = stack.pop();
26
return new MinusExpression(left, right);
27
default:
28
return new VariableExpression(c);
29
}
30
}
31
}
1
// PostfixExpression.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;
3
4
import java.util.Map;
5
6
public interface PostfixExpression {
7
int interpret(Map<Character, Integer> context);
8
}
1
// VariableExpression.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;
3
4
import java.util.Map;
5
6
public class VariableExpression implements PostfixExpression {
7
8
private Character character;
9
10
public VariableExpression(Character character) {
11
this.character = character;
12
}
13
14
@Override
15
public int interpret(Map<Character, Integer> context) {
16
return context.get(this.character);
17
}
18
}
1
// PlusExpression.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;
3
4
import java.util.Map;
5
6
public class PlusExpression implements PostfixExpression {
7
8
private PostfixExpression left;
9
10
private PostfixExpression right;
11
12
public PlusExpression(PostfixExpression left, PostfixExpression right) {
13
this.left = left;
14
this.right = right;
15
}
16
17
@Override
18
public int interpret(Map<Character, Integer> context) {
19
return left.interpret(context) + right.interpret(context);
20
}
21
}
1
// MinusExpression.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;
3
4
import java.util.Map;
5
6
public class MinusExpression implements PostfixExpression {
7
8
private PostfixExpression left;
9
10
private PostfixExpression right;
11
12
public MinusExpression(PostfixExpression left, PostfixExpression right) {
13
this.left = left;
14
this.right = right;
15
}
16
17
@Override
18
public int interpret(Map<Character, Integer> context) {
19
return left.interpret(context) - right.interpret(context);
20
}
21
}
1
// MultiplyExpression.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;
3
4
import java.util.Map;
5
6
public class MultiplyExpression implements PostfixExpression {
7
8
private PostfixExpression left, right;
9
10
public MultiplyExpression(PostfixExpression left, PostfixExpression right) {
11
this.left = left;
12
this.right = right;
13
}
14
15
@Override
16
public int interpret(Map<Character, Integer> context) {
17
return left.interpret(context) * right.interpret(context);
18
}
19
}
1
// App.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;
3
4
import java.util.Map;
5
6
public class App {
7
8
public static void main(String[] args) {
9
PostfixExpression expression = PostfixParser.parse("xyz+-a+");
10
int result = expression.interpret(Map.of('x', 1, 'y', 2, 'z', 3, 'a', 4));
11
System.out.println(result);
12
}
13
}

3.4 예시 - 변경 방법2

1
// PostfixExpression.java
2
public interface PostfixExpression {
3
int interpret(Map<Character, Integer> context);
4
5
static PostfixExpression plus(
6
PostfixExpression left,
7
PostfixExpression right
8
) {
9
return context -> left.interpret(context) + right.interpret(context);
10
}
11
12
static PostfixExpression minus(
13
PostfixExpression left,
14
PostfixExpression right
15
) {
16
return context -> left.interpret(context) - right.interpret(context);
17
}
18
19
static PostfixExpression variable(Character c) {
20
return context -> context.get(c);
21
}
22
}
1
// PostfixParser.java
2
public class PostfixParser {
3
4
public static PostfixExpression parse(String expression) {
5
Stack<PostfixExpression> stack = new Stack<>();
6
for (char c : expression.toCharArray()) {
7
stack.push(getExpression(c, stack));
8
}
9
return stack.pop();
10
}
11
12
private static PostfixExpression getExpression(
13
char c,
14
Stack<PostfixExpression> stack
15
) {
16
switch (c) {
17
case '+':
18
return PostfixExpression.plus(stack.pop(), stack.pop());
19
case '-':
20
PostfixExpression right = stack.pop();
21
PostfixExpression left = stack.pop();
22
return PostfixExpression.minus(left, right);
23
default:
24
return PostfixExpression.variable(c);
25
}
26
}
27
}

3.5 장단점

  • 장점
    • 자주 등장하는 문제 패턴을 언어와 문법으로 정의할 수 있다.
    • 기존 코드를 변경하지 않고 새로운 Expression을 추가할 수 있다.
  • 단점 : 복잡한 문법을 표현하려면 Expression과 Parser가 복잡해진다

3.6 실무에서 어떻게 쓰이나?

자바

  • 자바 컴파일러
  • 정규 표현식
1
// InterpreterInJava.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._03_java;
3
4
import java.util.regex.Pattern;
5
6
public class InterpreterInJava {
7
8
public static void main(String[] args) {
9
System.out.println(Pattern.matches(".pr...", "spring"));
10
System.out.println(Pattern.matches("[a-z]{6}", "spring"));
11
System.out.println(Pattern.matches("white[a-z]{4}[0-9]{4}", "whiteship2000"));
12
System.out.println(Pattern.matches("\\d", "1")); // one digit
13
System.out.println(Pattern.matches("\\D", "a")); // one non-digit
14
}
15
}

스프링

  • SpEL (스프링 Expression Language)
1
// InterpreterInSpring.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._03_java;
3
4
import org.springframework.expression.Expression;
5
import org.springframework.expression.ExpressionParser;
6
import org.springframework.expression.spel.standard.SpelExpressionParser;
7
8
public class InterpreterInSpring {
9
10
public static void main(String[] args) {
11
Book book = new Book("spring");
12
13
ExpressionParser parser = new SpelExpressionParser();
14
Expression expression = parser.parseExpression("title");
15
System.out.println(expression.getValue(book));
16
}
17
}
1
// MyService.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._03_java;
3
4
import org.springframework.beans.factory.annotation.Value;
5
import org.springframework.boot.ApplicationArguments;
6
import org.springframework.boot.ApplicationRunner;
7
import org.springframework.stereotype.Service;
8
9
@Service
10
public class MyService implements ApplicationRunner {
11
12
@Value("#{2 + 5}")
13
private String value;
14
15
@Override
16
public void run(ApplicationArguments args) throws Exception {
17
System.out.println(value);
18
}
19
}

4. 이터레이터(Interator) 패턴

4.1 패턴 소개

gof_3_8

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

gof_3_9


4.2 예시 - 기존

1
// Post.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before;
3
4
import java.time.LocalDateTime;
5
6
public class Post {
7
8
private String title;
9
10
private LocalDateTime createdDateTime;
11
12
public Post(String title) {
13
this.title = title;
14
this.createdDateTime = LocalDateTime.now();
15
}
16
17
public String getTitle() {
18
return title;
19
}
20
21
public void setTitle(String title) {
22
this.title = title;
23
}
24
25
public LocalDateTime getCreatedDateTime() {
26
return createdDateTime;
27
}
28
29
public void setCreatedDateTime(LocalDateTime createdDateTime) {
30
this.createdDateTime = createdDateTime;
31
}
32
}
1
// Board.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before;
3
4
import java.util.ArrayList;
5
import java.util.List;
6
7
public class Board {
8
9
List<Post> posts = new ArrayList<>();
10
11
public List<Post> getPosts() {
12
return posts;
13
}
14
15
public void setPosts(List<Post> posts) {
16
this.posts = posts;
17
}
18
19
public void addPost(String content) {
20
this.posts.add(new Post(content));
21
}
22
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before;
3
4
import java.util.Collections;
5
import java.util.List;
6
7
public class Client {
8
9
public static void main(String[] args) {
10
Board board = new Board();
11
board.addPost("디자인 패턴 게임");
12
board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");
13
board.addPost(
14
"지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다."
15
);
16
17
// TODO 들어간 순서대로 순회하기
18
List<Post> posts = board.getPosts();
19
for (int i = 0; i < posts.size(); i++) {
20
Post post = posts.get(i);
21
System.out.println(post.getTitle());
22
}
23
24
// TODO 가장 최신 글 먼저 순회하기
25
Collections.sort(
26
posts,
27
(p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime())
28
);
29
for (int i = 0; i < posts.size(); i++) {
30
Post post = posts.get(i);
31
System.out.println(post.getTitle());
32
}
33
}
34
}

4.3 예시 - 변경

1
// RecentPostIterator.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after;
3
4
import java.util.Collections;
5
import java.util.Iterator;
6
import java.util.List;
7
import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before.Post;
8
9
public class RecentPostIterator implements Iterator<Post> {
10
11
private Iterator<Post> internalIterator;
12
13
public RecentPostIterator(List<Post> posts) {
14
Collections.sort(
15
posts,
16
(p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime())
17
);
18
this.internalIterator = posts.iterator();
19
}
20
21
@Override
22
public boolean hasNext() {
23
return this.internalIterator.hasNext();
24
}
25
26
@Override
27
public Post next() {
28
return this.internalIterator.next();
29
}
30
}
1
// Board.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after;
3
4
import java.util.ArrayList;
5
import java.util.Iterator;
6
import java.util.List;
7
import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before.Post;
8
9
public class Board {
10
11
List<Post> posts = new ArrayList<>();
12
13
public List<Post> getPosts() {
14
return posts;
15
}
16
17
public void addPost(String content) {
18
this.posts.add(new Post(content));
19
}
20
21
public Iterator<Post> getRecentPostIterator() {
22
return new RecentPostIterator(this.posts);
23
}
24
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after;
3
4
import java.util.Iterator;
5
import java.util.List;
6
import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._01_before.Post;
7
8
public class Client {
9
10
public static void main(String[] args) {
11
Board board = new Board();
12
board.addPost("디자인 패턴 게임");
13
board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");
14
board.addPost(
15
"지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다."
16
);
17
18
// TODO 들어간 순서대로 순회하기
19
List<Post> posts = board.getPosts();
20
Iterator<Post> iterator = posts.iterator();
21
System.out.println(iterator.getClass());
22
23
for (int i = 0; i < posts.size(); i++) {
24
Post post = posts.get(i);
25
System.out.println(post.getTitle());
26
}
27
28
// TODO 가장 최신 글 먼저 순회하기
29
Iterator<Post> recentPostIterator = board.getRecentPostIterator();
30
while (recentPostIterator.hasNext()) {
31
System.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.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._03_java;
3
4
import java.io.FileInputStream;
5
import java.io.FileNotFoundException;
6
import java.util.Enumeration;
7
import java.util.Iterator;
8
import javax.xml.namespace.QName;
9
import javax.xml.stream.XMLEventReader;
10
import javax.xml.stream.XMLInputFactory;
11
import javax.xml.stream.XMLStreamException;
12
import javax.xml.stream.events.Attribute;
13
import javax.xml.stream.events.StartElement;
14
import javax.xml.stream.events.XMLEvent;
15
import me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._02_after.Board;
16
17
public class IteratorInJava {
18
19
public static void main(String[] args)
20
throws FileNotFoundException, XMLStreamException {
21
Enumeration enumeration;
22
Iterator iterator;
23
24
Board board = new Board();
25
board.addPost("디자인 패턴 게임");
26
board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");
27
board.addPost(
28
"지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다."
29
);
30
31
// board.getPosts().iterator().forEachRemaining(p -> System.out.println(p.getTitle()));
32
33
// TODO Streaming API for XML(StAX), 이터레이터 기반의 API
34
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
35
XMLEventReader reader = xmlInputFactory.createXMLEventReader(
36
new FileInputStream("Book.xml")
37
);
38
39
while (reader.hasNext()) {
40
XMLEvent nextEvent = reader.nextEvent();
41
if (nextEvent.isStartElement()) {
42
StartElement startElement = nextEvent.asStartElement();
43
QName name = startElement.getName();
44
if (name.getLocalPart().equals("book")) {
45
Attribute title = startElement.getAttributeByName(new QName("title"));
46
System.out.println(title.getValue());
47
}
48
}
49
}
50
}
51
}

스프링

  • CompositeIterator
1
// IteratorInSpring.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._16_iterator._03_java;
3
4
import org.springframework.util.CompositeIterator;
5
6
public class IteratorInSpring {
7
8
public static void main(String[] args) {
9
CompositeIterator iterator;
10
}
11
}

5. 중재자(Mediator) 패턴

5.1 패턴 소개

gof_3_10

여러 객체들이 소통하는 방법을 캡슐화하는 패턴.

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

gof_3_11


5.2 예시 - 기존

1
// Guest.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;
3
4
public class Guest {
5
6
private Restaurant restaurant = new Restaurant();
7
8
private CleaningService cleaningService = new CleaningService();
9
10
public void dinner() {
11
restaurant.dinner(this);
12
}
13
14
public void getTower(int numberOfTower) {
15
cleaningService.getTower(this, numberOfTower);
16
}
17
}
1
// Gym.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;
3
4
public class Gym {
5
6
private CleaningService cleaningService;
7
8
public void clean() {
9
cleaningService.clean(this);
10
}
11
}
1
// Restaurant.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;
3
4
public class Restaurant {
5
6
private CleaningService cleaningService = new CleaningService();
7
8
public void dinner(Guest guest) {
9
System.out.println("dinner " + guest);
10
}
11
12
public void clean() {
13
cleaningService.clean(this);
14
}
15
}
1
// CleaningService.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;
3
4
public class CleaningService {
5
6
public void clean(Gym gym) {
7
System.out.println("clean " + gym);
8
}
9
10
public void getTower(Guest guest, int numberOfTower) {
11
System.out.println(numberOfTower + " towers to " + guest);
12
}
13
14
public void clean(Restaurant restaurant) {
15
System.out.println("clean " + restaurant);
16
}
17
}
1
// Hotel.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._01_before;
3
4
public class Hotel {
5
6
public static void main(String[] args) {
7
Guest guest = new Guest();
8
guest.getTower(3);
9
guest.dinner();
10
11
Restaurant restaurant = new Restaurant();
12
restaurant.clean();
13
}
14
}

5.3 예시 - 변경

1
// CleaningService.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;
3
4
public class CleaningService {
5
6
private FrontDesk frontDesk = new FrontDesk();
7
8
public void getTowers(Integer guestId, int numberOfTowers) {
9
String roomNumber = this.frontDesk.getRoomNumberFor(guestId);
10
System.out.println("provide " + numberOfTowers + " to " + roomNumber);
11
}
12
}
1
// Guest.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;
3
4
import java.time.LocalDateTime;
5
6
public class Guest {
7
8
private Integer id;
9
10
private FrontDesk frontDesk = new FrontDesk();
11
12
public void getTowers(int numberOfTowers) {
13
this.frontDesk.getTowers(this, numberOfTowers);
14
}
15
16
private void dinner(LocalDateTime dateTime) {
17
this.frontDesk.dinner(this, dateTime);
18
}
19
20
public Integer getId() {
21
return id;
22
}
23
24
public void setId(Integer id) {
25
this.id = id;
26
}
27
}
1
// Restaurant.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;
3
4
import java.time.LocalDateTime;
5
6
public class Restaurant {
7
8
public void dinner(Integer id, LocalDateTime dateTime) {}
9
}
1
// FrontDesk.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._17_mediator._02_after;
3
4
import java.time.LocalDateTime;
5
6
public class FrontDesk {
7
8
private CleaningService cleaningService = new CleaningService();
9
10
private Restaurant restaurant = new Restaurant();
11
12
public void getTowers(Guest guest, int numberOfTowers) {
13
cleaningService.getTowers(guest.getId(), numberOfTowers);
14
}
15
16
public String getRoomNumberFor(Integer guestId) {
17
return "1111";
18
}
19
20
public void dinner(Guest guest, LocalDateTime dateTime) {
21
restaurant.dinner(guest.getId(), dateTime);
22
}
23
}

5.4 장단점

  • 장점
    • 컴포넌트 코드를 변경하지 않고 새로운 중재자를 만들어 사용할 수 있다.
    • 각각의 컴포넌트 코드를 보다 간결하게 유지할 수 있다.
  • 단점
    • 중재자 역할을 하는 클래스의 복잡도와 결합도가 증가한다

5.5 실무에서 어떻게 쓰이나?

gof_3_12

  • 자바
    • ExecutorService
    • Executor
  • 스프링
    • DispatcherServlet

6. 메멘토(Mediator) 패턴

6.1 패턴 소개

gof_3_13

캡슐화를 유지하면서 객체 내부 상태를 외부에 저장하는 방법.

  • 객체 상태를 외부에 저장했다가 해당 상태로 다시 복구할 수 있다.

gof_3_14


6.2 예시 - 기존

1
// Game.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._01_before;
3
4
import java.io.Serializable;
5
6
public class Game implements Serializable {
7
8
private int redTeamScore;
9
10
private int blueTeamScore;
11
12
public int getRedTeamScore() {
13
return redTeamScore;
14
}
15
16
public void setRedTeamScore(int redTeamScore) {
17
this.redTeamScore = redTeamScore;
18
}
19
20
public int getBlueTeamScore() {
21
return blueTeamScore;
22
}
23
24
public void setBlueTeamScore(int blueTeamScore) {
25
this.blueTeamScore = blueTeamScore;
26
}
27
}
28
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._01_before;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
Game game = new Game();
8
game.setRedTeamScore(10);
9
game.setBlueTeamScore(20);
10
11
int blueTeamScore = game.getBlueTeamScore();
12
int redTeamScore = game.getRedTeamScore();
13
14
Game restoredGame = new Game();
15
restoredGame.setBlueTeamScore(blueTeamScore);
16
restoredGame.setRedTeamScore(redTeamScore);
17
}
18
}

6.3 예시 - 변경

1
// GameSave.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._02_after;
3
4
public final class GameSave {
5
6
private final int blueTeamScore;
7
8
private final int redTeamScore;
9
10
public GameSave(int blueTeamScore, int redTeamScore) {
11
this.blueTeamScore = blueTeamScore;
12
this.redTeamScore = redTeamScore;
13
}
14
15
public int getBlueTeamScore() {
16
return blueTeamScore;
17
}
18
19
public int getRedTeamScore() {
20
return redTeamScore;
21
}
22
}
1
// Game.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._02_after;
3
4
public class Game {
5
6
private int redTeamScore;
7
8
private int blueTeamScore;
9
10
public int getRedTeamScore() {
11
return redTeamScore;
12
}
13
14
public void setRedTeamScore(int redTeamScore) {
15
this.redTeamScore = redTeamScore;
16
}
17
18
public int getBlueTeamScore() {
19
return blueTeamScore;
20
}
21
22
public void setBlueTeamScore(int blueTeamScore) {
23
this.blueTeamScore = blueTeamScore;
24
}
25
26
public GameSave save() {
27
return new GameSave(this.blueTeamScore, this.redTeamScore);
28
}
29
30
public void restore(GameSave gameSave) {
31
this.blueTeamScore = gameSave.getBlueTeamScore();
32
this.redTeamScore = gameSave.getRedTeamScore();
33
}
34
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._02_after;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
Game game = new Game();
8
game.setBlueTeamScore(10);
9
game.setRedTeamScore(20);
10
11
GameSave save = game.save();
12
13
game.setBlueTeamScore(12);
14
game.setRedTeamScore(22);
15
16
game.restore(save);
17
18
System.out.println(game.getBlueTeamScore());
19
System.out.println(game.getRedTeamScore());
20
}
21
}

6.4 장단점

  • 장점
    • 캡슐화를 지키면서 상태 객체 상태 스냅샷을 만들 수 있다.
    • 객체 상태 저장하고 또는 복원하는 역할을 CareTaker에게 위임할 수 있다.
    • 객체 상태가 바뀌어도 클라이언트 코드는 변경되지 않는다.
  • 단점
    • 많은 정보를 저장하는 Mementor를 자주 생성하는 경우 메모리 사용량에 많은 영향을 줄 수 있 다

6.5 실무에서 어떻게 쓰이나?

자바

  • 객체 직렬화, java.io.Serializable
  • java.util.Date
1
// MementoInJava.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._18_memento._03_java;
3
4
import java.io.*;
5
import me.whiteship.designpatterns._03_behavioral_patterns._18_memento._01_before.Game;
6
7
public class MementoInJava {
8
9
public static void main(String[] args)
10
throws IOException, ClassNotFoundException {
11
// TODO Serializable
12
Game game = new Game();
13
game.setRedTeamScore(10);
14
game.setBlueTeamScore(20);
15
16
// TODO 직렬화
17
try (
18
FileOutputStream fileOut = new FileOutputStream("GameSave.hex");
19
ObjectOutputStream out = new ObjectOutputStream(fileOut)
20
) {
21
out.writeObject(game);
22
}
23
24
game.setBlueTeamScore(25);
25
game.setRedTeamScore(15);
26
27
// TODO 역직렬화
28
try (
29
FileInputStream fileIn = new FileInputStream("GameSave.hex");
30
ObjectInputStream in = new ObjectInputStream(fileIn)
31
) {
32
game = (Game) in.readObject();
33
System.out.println(game.getBlueTeamScore());
34
System.out.println(game.getRedTeamScore());
35
}
36
}
37
}

8. 옵저버(Observer) 패턴

8.1 패턴 소개

gof_3_15

다수의 객체가 특정 객체 상태 변화를 감지하고 알림을 받는 패턴.

  • 발행(publish)-구독(subscribe) 패턴을 구현할 수 있다.

gof_3_16


8.2 예시 - 기존

1
// ChatServer.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._01_before;
3
4
import java.util.ArrayList;
5
import java.util.HashMap;
6
import java.util.List;
7
import java.util.Map;
8
9
public class ChatServer {
10
11
private Map<String, List<String>> messages;
12
13
public ChatServer() {
14
this.messages = new HashMap<>();
15
}
16
17
public void add(String subject, String message) {
18
if (messages.containsKey(subject)) {
19
messages.get(subject).add(message);
20
} else {
21
List<String> messageList = new ArrayList<>();
22
messageList.add(message);
23
messages.put(subject, messageList);
24
}
25
}
26
27
public List<String> getMessage(String subject) {
28
return messages.get(subject);
29
}
30
}
1
// User.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._01_before;
3
4
import java.util.List;
5
6
public class User {
7
8
private ChatServer chatServer;
9
10
public User(ChatServer chatServer) {
11
this.chatServer = chatServer;
12
}
13
14
public void sendMessage(String subject, String message) {
15
chatServer.add(subject, message);
16
}
17
18
public List<String> getMessage(String subject) {
19
return chatServer.getMessage(subject);
20
}
21
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._01_before;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
ChatServer chatServer = new ChatServer();
8
9
User user1 = new User(chatServer);
10
user1.sendMessage("디자인패턴", "이번엔 옵저버 패턴입니다.");
11
user1.sendMessage("롤드컵2021", "LCK 화이팅!");
12
13
User user2 = new User(chatServer);
14
System.out.println(user2.getMessage("디자인패턴"));
15
16
user1.sendMessage("디자인패턴", "예제 코드 보는 중..");
17
System.out.println(user2.getMessage("디자인패턴"));
18
}
19
}

8.3 예시 - 변경

1
// Subscriber.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;
3
4
public interface Subscriber {
5
void handleMessage(String message);
6
}
1
// User.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;
3
4
public class User implements Subscriber {
5
6
private String name;
7
8
public User(String name) {
9
this.name = name;
10
}
11
12
public String getName() {
13
return name;
14
}
15
16
@Override
17
public void handleMessage(String message) {
18
System.out.println(message);
19
}
20
}
1
// ChatServer.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;
3
4
import java.util.ArrayList;
5
import java.util.HashMap;
6
import java.util.List;
7
import java.util.Map;
8
9
public class ChatServer {
10
11
private Map<String, List<Subscriber>> subscribers = new HashMap<>();
12
13
public void register(String subject, Subscriber subscriber) {
14
if (this.subscribers.containsKey(subject)) {
15
this.subscribers.get(subject).add(subscriber);
16
} else {
17
List<Subscriber> list = new ArrayList<>();
18
list.add(subscriber);
19
this.subscribers.put(subject, list);
20
}
21
}
22
23
public void unregister(String subject, Subscriber subscriber) {
24
if (this.subscribers.containsKey(subject)) {
25
this.subscribers.get(subject).remove(subscriber);
26
}
27
}
28
29
public void sendMessage(User user, String subject, String message) {
30
if (this.subscribers.containsKey(subject)) {
31
String userMessage = user.getName() + ": " + message;
32
this.subscribers.get(subject).forEach(s -> s.handleMessage(userMessage));
33
}
34
}
35
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._02_after;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
ChatServer chatServer = new ChatServer();
8
User user1 = new User("keesun");
9
User user2 = new User("whiteship");
10
11
chatServer.register("오징어게임", user1);
12
chatServer.register("오징어게임", user2);
13
14
chatServer.register("디자인패턴", user1);
15
16
chatServer.sendMessage(
17
user1,
18
"오징어게임",
19
"아.. 이름이 기억났어.. 일남이야.. 오일남"
20
);
21
chatServer.sendMessage(user2, "디자인패턴", "옵저버 패턴으로 만든 채팅");
22
23
chatServer.unregister("디자인패턴", user2);
24
25
chatServer.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.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;
3
4
import java.util.Observable;
5
import java.util.Observer;
6
7
public class ObserverInJava {
8
9
static class User implements Observer {
10
11
@Override
12
public void update(Observable o, Object arg) {
13
System.out.println(arg);
14
}
15
}
16
17
static class Subject extends Observable {
18
19
public void add(String message) {
20
setChanged();
21
notifyObservers(message);
22
}
23
}
24
25
public static void main(String[] args) {
26
Subject subject = new Subject();
27
User user = new User();
28
subject.addObserver(user);
29
subject.add("Hello Java, Observer");
30
}
31
}
1
// PropertyChangeExample.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;
3
4
import java.beans.PropertyChangeEvent;
5
import java.beans.PropertyChangeListener;
6
import java.beans.PropertyChangeSupport;
7
8
public class PropertyChangeExample {
9
10
static class User implements PropertyChangeListener {
11
12
@Override
13
public void propertyChange(PropertyChangeEvent evt) {
14
System.out.println(evt.getNewValue());
15
}
16
}
17
18
static class Subject {
19
20
PropertyChangeSupport support = new PropertyChangeSupport(this);
21
22
public void addObserver(PropertyChangeListener observer) {
23
support.addPropertyChangeListener(observer);
24
}
25
26
public void removeObserver(PropertyChangeListener observer) {
27
support.removePropertyChangeListener(observer);
28
}
29
30
public void add(String message) {
31
support.firePropertyChange("eventName", null, message);
32
}
33
}
34
35
public static void main(String[] args) {
36
Subject subject = new Subject();
37
User observer = new User();
38
subject.addObserver(observer);
39
subject.add("자바 PCL 예제 코드");
40
subject.removeObserver(observer);
41
subject.add("이 메시지는 볼 수 없지..");
42
}
43
}
1
// FlowInJava.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;
3
4
import java.util.concurrent.Flow;
5
import java.util.concurrent.SubmissionPublisher;
6
7
public class FlowInJava {
8
9
public static void main(String[] args) throws InterruptedException {
10
Flow.Publisher<String> publisher = new SubmissionPublisher<>();
11
12
Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() {
13
private Flow.Subscription subscription;
14
15
@Override
16
public void onSubscribe(Flow.Subscription subscription) {
17
System.out.println("sub!");
18
this.subscription = subscription;
19
this.subscription.request(1);
20
}
21
22
@Override
23
public void onNext(String item) {
24
System.out.println("onNext called");
25
System.out.println(Thread.currentThread().getName());
26
System.out.println(item);
27
}
28
29
@Override
30
public void onError(Throwable throwable) {}
31
32
@Override
33
public void onComplete() {
34
System.out.println("completed");
35
}
36
};
37
38
publisher.subscribe(subscriber);
39
40
((SubmissionPublisher) publisher).submit("hello java");
41
42
System.out.println("이게 먼저 출력될 수도 있습니다.");
43
}
44
}

스프링

  • ApplicationContext와 ApplicationEvent
1
// MyEvent.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;
3
4
public class MyEvent {
5
6
private String message;
7
8
public MyEvent(String message) {
9
this.message = message;
10
}
11
12
public String getMessage() {
13
return message;
14
}
15
}
1
// MyEventListener.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;
3
4
import org.springframework.context.event.EventListener;
5
import org.springframework.stereotype.Component;
6
7
@Component
8
public class MyEventListener {
9
10
@EventListener(MyEvent.class)
11
public void onApplicationEvent(MyEvent event) {
12
System.out.println(event.getMessage());
13
}
14
}
1
// MyRunner.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._19_observer._03_java;
3
4
import org.springframework.boot.ApplicationArguments;
5
import org.springframework.boot.ApplicationRunner;
6
import org.springframework.context.ApplicationEventPublisher;
7
import org.springframework.stereotype.Component;
8
9
@Component
10
public class MyRunner implements ApplicationRunner {
11
12
private ApplicationEventPublisher publisher;
13
14
public MyRunner(ApplicationEventPublisher publisher) {
15
this.publisher = publisher;
16
}
17
18
@Override
19
public void run(ApplicationArguments args) throws Exception {
20
publisher.publishEvent(new MyEvent("hello spring event"));
21
}
22
}

9. 상태(State) 패턴

9.1 패턴 소개

gof_3_17

객체 내부 상태 변경에 따라 객체의 행동이 달라지는 패턴.

  • 상태에 특화된 행동들을 분리해 낼 수 있으며, 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.

gof_3_18


9.2 예시 - 기존

1
// Student.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._01_before;
3
4
import java.util.ArrayList;
5
import java.util.List;
6
7
public class Student {
8
9
private String name;
10
11
public Student(String name) {
12
this.name = name;
13
}
14
15
private List<OnlineCourse> privateCourses = new ArrayList<>();
16
17
public boolean isEnabledForPrivateClass(OnlineCourse onlineCourse) {
18
return privateCourses.contains(onlineCourse);
19
}
20
21
public void addPrivateCourse(OnlineCourse onlineCourse) {
22
this.privateCourses.add(onlineCourse);
23
}
24
25
@Override
26
public String toString() {
27
return "Student{" + "name='" + name + '\'' + '}';
28
}
29
}
1
// OnlineCourse.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._01_before;
3
4
import java.util.ArrayList;
5
import java.util.List;
6
7
public class OnlineCourse {
8
9
public enum State {
10
DRAFT,
11
PUBLISHED,
12
PRIVATE,
13
}
14
15
private State state = State.DRAFT;
16
17
private List<String> reviews = new ArrayList<>();
18
19
private List<Student> students = new ArrayList<>();
20
21
public void addReview(String review, Student student) {
22
if (this.state == State.PUBLISHED) {
23
this.reviews.add(review);
24
} else if (this.state == State.PRIVATE && this.students.contains(student)) {
25
this.reviews.add(review);
26
} else {
27
throw new UnsupportedOperationException("리뷰를 작성할 수 없습니다.");
28
}
29
}
30
31
public void addStudent(Student student) {
32
if (this.state == State.DRAFT || this.state == State.PUBLISHED) {
33
this.students.add(student);
34
} else if (this.state == State.PRIVATE && availableTo(student)) {
35
this.students.add(student);
36
} else {
37
throw new UnsupportedOperationException(
38
"학생을 해당 수업에 추가할 수 없습니다."
39
);
40
}
41
42
if (this.students.size() > 1) {
43
this.state = State.PRIVATE;
44
}
45
}
46
47
public void changeState(State newState) {
48
this.state = newState;
49
}
50
51
public State getState() {
52
return state;
53
}
54
55
public List<String> getReviews() {
56
return reviews;
57
}
58
59
public List<Student> getStudents() {
60
return students;
61
}
62
63
private boolean availableTo(Student student) {
64
return student.isEnabledForPrivateClass(this);
65
}
66
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._01_before;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
Student student = new Student("whiteship");
8
OnlineCourse onlineCourse = new OnlineCourse();
9
10
Student keesun = new Student("keesun");
11
keesun.addPrivateCourse(onlineCourse);
12
13
onlineCourse.addStudent(student);
14
onlineCourse.changeState(OnlineCourse.State.PRIVATE);
15
16
onlineCourse.addStudent(keesun);
17
18
onlineCourse.addReview("hello", student);
19
20
System.out.println(onlineCourse.getState());
21
System.out.println(onlineCourse.getStudents());
22
System.out.println(onlineCourse.getReviews());
23
}
24
}

9.3 예시 - 변경

1
// State.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;
3
4
public interface State {
5
void addReview(String review, Student student);
6
7
void addStudent(Student student);
8
}
1
// Draft.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;
3
4
public class Draft implements State {
5
6
private OnlineCourse onlineCourse;
7
8
public Draft(OnlineCourse onlineCourse) {
9
this.onlineCourse = onlineCourse;
10
}
11
12
@Override
13
public void addReview(String review, Student student) {
14
throw new UnsupportedOperationException(
15
"드래프트 상태에서는 리뷰를 남길 수 없습니다."
16
);
17
}
18
19
@Override
20
public void addStudent(Student student) {
21
this.onlineCourse.getStudents().add(student);
22
if (this.onlineCourse.getStudents().size() > 1) {
23
this.onlineCourse.changeState(new Private(this.onlineCourse));
24
}
25
}
26
}
1
// Private.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;
3
4
public class Private implements State {
5
6
private OnlineCourse onlineCourse;
7
8
public Private(OnlineCourse onlineCourse) {
9
this.onlineCourse = onlineCourse;
10
}
11
12
@Override
13
public void addReview(String review, Student student) {
14
if (this.onlineCourse.getStudents().contains(student)) {
15
this.onlineCourse.getReviews().add(review);
16
} else {
17
throw new UnsupportedOperationException(
18
"프라이빗 코스를 수강하는 학생만 리뷰를 남길 수 있습니다."
19
);
20
}
21
}
22
23
@Override
24
public void addStudent(Student student) {
25
if (student.isAvailable(this.onlineCourse)) {
26
this.onlineCourse.getStudents().add(student);
27
} else {
28
throw new UnsupportedOperationException(
29
"프라이빛 코스를 수강할 수 없습니다."
30
);
31
}
32
}
33
}
1
// Published.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;
3
4
public class Published implements State {
5
6
private OnlineCourse onlineCourse;
7
8
public Published(OnlineCourse onlineCourse) {
9
this.onlineCourse = onlineCourse;
10
}
11
12
@Override
13
public void addReview(String review, Student student) {
14
this.onlineCourse.getReviews().add(review);
15
}
16
17
@Override
18
public void addStudent(Student student) {
19
this.onlineCourse.getStudents().add(student);
20
}
21
}
1
// OnlineCourse.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;
3
4
import java.util.ArrayList;
5
import java.util.List;
6
7
public class OnlineCourse {
8
9
private State state = new Draft(this);
10
11
private List<Student> students = new ArrayList<>();
12
13
private List<String> reviews = new ArrayList<>();
14
15
public void addStudent(Student student) {
16
this.state.addStudent(student);
17
}
18
19
public void addReview(String review, Student student) {
20
this.state.addReview(review, student);
21
}
22
23
public State getState() {
24
return state;
25
}
26
27
public List<Student> getStudents() {
28
return students;
29
}
30
31
public List<String> getReviews() {
32
return reviews;
33
}
34
35
public void changeState(State state) {
36
this.state = state;
37
}
38
}
1
// Student.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;
3
4
import java.util.HashSet;
5
import java.util.Set;
6
7
public class Student {
8
9
private String name;
10
11
public Student(String name) {
12
this.name = name;
13
}
14
15
private Set<OnlineCourse> onlineCourses = new HashSet<>();
16
17
public boolean isAvailable(OnlineCourse onlineCourse) {
18
return onlineCourses.contains(onlineCourse);
19
}
20
21
public void addPrivate(OnlineCourse onlineCourse) {
22
this.onlineCourses.add(onlineCourse);
23
}
24
25
@Override
26
public String toString() {
27
return "Student{" + "name='" + name + '\'' + '}';
28
}
29
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._20_state._02_after;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
OnlineCourse onlineCourse = new OnlineCourse();
8
Student student = new Student("whiteship");
9
Student keesun = new Student("keesun");
10
keesun.addPrivate(onlineCourse);
11
12
onlineCourse.addStudent(student);
13
14
onlineCourse.changeState(new Private(onlineCourse));
15
16
onlineCourse.addReview("hello", student);
17
18
onlineCourse.addStudent(keesun);
19
20
System.out.println(onlineCourse.getState());
21
System.out.println(onlineCourse.getReviews());
22
System.out.println(onlineCourse.getStudents());
23
}
24
}

9.4 장단점

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

10. 전략(Strategy) 패턴

10.1 패턴 소개

gof_3_19

여러 알고리즘을 캡슐화하고 상호 교환 가능하게 만드는 패턴.

  • 컨텍스트에서 사용할 알고리듬을 클라이언트 선택한다.

gof_3_20


10.2 예시 - 기존

1
// BlueLightRedLight.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._01_before;
3
4
public class BlueLightRedLight {
5
6
private int speed;
7
8
public BlueLightRedLight(int speed) {
9
this.speed = speed;
10
}
11
12
public void blueLight() {
13
if (speed == 1) {
14
System.out.println("무 궁 화 꽃 이");
15
} else if (speed == 2) {
16
System.out.println("무궁화꽃이");
17
} else {
18
System.out.println("무광꼬치");
19
}
20
}
21
22
public void redLight() {
23
if (speed == 1) {
24
System.out.println("피 었 습 니 다.");
25
} else if (speed == 2) {
26
System.out.println("피었습니다.");
27
} else {
28
System.out.println("피어씀다");
29
}
30
}
31
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._01_before;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
BlueLightRedLight blueLightRedLight = new BlueLightRedLight(3);
8
blueLightRedLight.blueLight();
9
blueLightRedLight.redLight();
10
}
11
}

10.3 예시 - 변경

1
// Speed.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;
3
4
public interface Speed {
5
void blueLight();
6
7
void redLight();
8
}
1
// Normal.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;
3
4
public class Normal implements Speed {
5
6
@Override
7
public void blueLight() {
8
System.out.println("무 궁 화 꽃 이");
9
}
10
11
@Override
12
public void redLight() {
13
System.out.println("피 었 습 니 다.");
14
}
15
}
1
// Faster.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;
3
4
public class Faster implements Speed {
5
6
@Override
7
public void blueLight() {
8
System.out.println("무궁화꽃이");
9
}
10
11
@Override
12
public void redLight() {
13
System.out.println("피었습니다.");
14
}
15
}
1
// Fastest.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;
3
4
public class Fastest implements Speed {
5
6
@Override
7
public void blueLight() {
8
System.out.println("무광꼬치");
9
}
10
11
@Override
12
public void redLight() {
13
System.out.println("피어씀다.");
14
}
15
}
1
// BlueLightRedLight.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;
3
4
public class BlueLightRedLight {
5
6
public void blueLight(Speed speed) {
7
speed.blueLight();
8
}
9
10
public void redLight(Speed speed) {
11
speed.redLight();
12
}
13
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._02_after;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
BlueLightRedLight game = new BlueLightRedLight();
8
game.blueLight(new Normal());
9
game.redLight(new Fastest());
10
game.blueLight(
11
new Speed() {
12
@Override
13
public void blueLight() {
14
System.out.println("blue light");
15
}
16
17
@Override
18
public void redLight() {
19
System.out.println("red light");
20
}
21
}
22
);
23
}
24
}

10.4 장단점

  • 장점
    • 새로운 전략을 추가하더라도 기존 코드를 변경하지 않는다.
    • 상속 대신 위임을 사용할 수 있다.
    • 런타임에 전략을 변경할 수 있다.
  • 단점
    • 복잡도가 증가한다.
    • 클라이언트 코드가 구체적인 전략을 알아야 한다

10.5 실무에서 어떻게 쓰이나?

자바의 Comparator

1
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._03_java;
2
3
import java.util.ArrayList;
4
import java.util.Collections;
5
import java.util.Comparator;
6
import java.util.List;
7
8
public class StrategyInJava {
9
10
public static void main(String[] args) {
11
List<Integer> numbers = new ArrayList<>();
12
numbers.add(10);
13
numbers.add(5);
14
15
System.out.println(numbers);
16
17
Collections.sort(numbers, Comparator.naturalOrder());
18
19
System.out.println(numbers);
20
}
21
}

스프링

  • ApplicationContext
  • PlatformTransactionManager
1
// StrategyInSpring.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._21_strategy._03_java;
3
4
import org.springframework.beans.factory.xml.BeanDefinitionParser;
5
import org.springframework.cache.CacheManager;
6
import org.springframework.context.ApplicationContext;
7
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
8
import org.springframework.context.support.ClassPathXmlApplicationContext;
9
import org.springframework.context.support.FileSystemXmlApplicationContext;
10
import org.springframework.transaction.PlatformTransactionManager;
11
12
public class StrategyInSpring {
13
14
public static void main(String[] args) {
15
ApplicationContext applicationContext = new ClassPathXmlApplicationContext();
16
ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext();
17
ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext();
18
19
BeanDefinitionParser parser;
20
21
PlatformTransactionManager platformTransactionManager;
22
23
CacheManager cacheManager;
24
}
25
}

11. 템플릿 메소드(Template method) 패턴

11.1 패턴 소개

gof_3_21

알고리즘 구조를 서브 클래스가 확장할 수 있도록 템플릿으로 제공하는 방법.

  • 추상 클래스는 템플릿을 제공하고 하위 클래스는 구체적인 알고리듬을 제공한다

gof_3_22

템플릿 콜백 (Template-Callback)

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

gof_3_23


11.2 예시 - 기존

1
// FileProcessor.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._01_before;
3
4
import java.io.BufferedReader;
5
import java.io.FileReader;
6
import java.io.IOException;
7
8
public class FileProcessor {
9
10
private String path;
11
12
public FileProcessor(String path) {
13
this.path = path;
14
}
15
16
public int process() {
17
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
18
int result = 0;
19
String line = null;
20
while ((line = reader.readLine()) != null) {
21
result += Integer.parseInt(line);
22
}
23
return result;
24
} catch (IOException e) {
25
throw new IllegalArgumentException(
26
path + "에 해당하는 파일이 없습니다.",
27
e
28
);
29
}
30
}
31
}
1
// MultuplyFileProcessor.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._01_before;
3
4
import java.io.BufferedReader;
5
import java.io.FileReader;
6
import java.io.IOException;
7
8
public class MultuplyFileProcessor {
9
10
private String path;
11
12
public MultuplyFileProcessor(String path) {
13
this.path = path;
14
}
15
16
public int process() {
17
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
18
int result = 0;
19
String line = null;
20
while ((line = reader.readLine()) != null) {
21
result *= Integer.parseInt(line);
22
}
23
return result;
24
} catch (IOException e) {
25
throw new IllegalArgumentException(
26
path + "에 해당하는 파일이 없습니다.",
27
e
28
);
29
}
30
}
31
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._01_before;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
FileProcessor fileProcessor = new FileProcessor("number.txt");
8
int result = fileProcessor.process();
9
System.out.println(result);
10
}
11
}

11.3 예시 - 변경

1
// FileProcessor.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;
3
4
import java.io.BufferedReader;
5
import java.io.FileReader;
6
import java.io.IOException;
7
8
public abstract class FileProcessor {
9
10
private String path;
11
12
public FileProcessor(String path) {
13
this.path = path;
14
}
15
16
public final int process(Operator operator) {
17
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
18
int result = 0;
19
String line = null;
20
while ((line = reader.readLine()) != null) {
21
result = getResult(result, Integer.parseInt(line));
22
}
23
return result;
24
} catch (IOException e) {
25
throw new IllegalArgumentException(
26
path + "에 해당하는 파일이 없습니다.",
27
e
28
);
29
}
30
}
31
32
protected abstract int getResult(int result, int number);
33
}
1
// Multiply.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;
3
4
public class Multiply extends FileProcessor {
5
6
public Multiply(String path) {
7
super(path);
8
}
9
10
@Override
11
protected int getResult(int result, int number) {
12
return result *= number;
13
}
14
}
1
// Operator.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;
3
4
public interface Operator {
5
abstract int getResult(int result, int number);
6
}
1
// FileProcessor.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;
3
4
import java.io.BufferedReader;
5
import java.io.FileReader;
6
import java.io.IOException;
7
8
public abstract class FileProcessor {
9
10
private String path;
11
12
public FileProcessor(String path) {
13
this.path = path;
14
}
15
16
public final int process(Operator operator) {
17
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
18
int result = 0;
19
String line = null;
20
while ((line = reader.readLine()) != null) {
21
result = getResult(result, Integer.parseInt(line));
22
}
23
return result;
24
} catch (IOException e) {
25
throw new IllegalArgumentException(
26
path + "에 해당하는 파일이 없습니다.",
27
e
28
);
29
}
30
}
31
32
protected abstract int getResult(int result, int number);
33
}
1
// Plus.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;
3
4
public class Plus implements Operator {
5
6
@Override
7
public int getResult(int result, int number) {
8
return result += number;
9
}
10
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._02_after;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
FileProcessor fileProcessor = new Multiply("number.txt");
8
int result = fileProcessor.process((sum, number) -> sum += number);
9
System.out.println(result);
10
}
11
}

11.4 장단점

  • 장점
    • 템플릿 코드를 재사용하고 중복 코드를 줄일 수 있다.
    • 템플릿 코드를 변경하지 않고 상속을 받아서 구체적인 알고리듬만 변경할 수 있다.
  • 단점
    • 리스코프 치환 원칙을 위반할 수도 있다.
    • 알고리듬 구조가 복잡할 수록 템플릿을 유지하기 어려워진다

11.5 실무에서 어떻게 쓰이나?

자바의 HttpServlet

1
// MyHello.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._03_java;
3
4
import java.io.IOException;
5
import javax.servlet.ServletException;
6
import javax.servlet.http.HttpServlet;
7
import javax.servlet.http.HttpServletRequest;
8
import javax.servlet.http.HttpServletResponse;
9
10
public class MyHello extends HttpServlet {
11
12
@Override
13
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
14
throws ServletException, IOException {
15
super.doGet(req, resp);
16
}
17
18
@Override
19
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
20
throws ServletException, IOException {
21
super.doPost(req, resp);
22
}
23
}

스프링

  • 템플릿 메소드 패턴
    • Configuration
  • 템플릿 콜백 패턴
    • JdbcTemplate
    • RestTemplate
1
// TemplateInSpring.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._22_template._03_java;
3
4
import java.util.Arrays;
5
import org.springframework.context.annotation.Configuration;
6
import org.springframework.http.*;
7
import org.springframework.jdbc.core.JdbcTemplate;
8
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10
import org.springframework.web.client.RestTemplate;
11
12
public class TemplateInSpring {
13
14
public static void main(String[] args) {
15
// TODO 템플릿-콜백 패턴
16
// JdbcTemplate
17
JdbcTemplate jdbcTemplate = new JdbcTemplate();
18
jdbcTemplate.execute("insert");
19
20
// RestTemplate
21
RestTemplate restTemplate = new RestTemplate();
22
23
HttpHeaders headers = new HttpHeaders();
24
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
25
headers.set("X-COM-PERSIST", "NO");
26
headers.set("X-COM-LOCATION", "USA");
27
28
HttpEntity<String> entity = new HttpEntity<String>(headers);
29
ResponseEntity<String> responseEntity = restTemplate.exchange(
30
"http://localhost:8080/users",
31
HttpMethod.GET,
32
entity,
33
String.class
34
);
35
}
36
37
@Configuration
38
class SecurityConfig extends WebSecurityConfigurerAdapter {
39
40
@Override
41
protected void configure(HttpSecurity http) throws Exception {
42
http.authorizeRequests().anyRequest().permitAll();
43
}
44
}
45
}

12. 방문자(Visitor) 패턴

12.1 패턴 소개

gof_3_24

기존 코드를 변경하지 않고 새로운 기능을 추가하는 방법.

  • 더블 디스패치 (Double Dispatch)를 활용할 수 있다.

12.2 예시 - 기존

1
// Device.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;
3
4
public interface Device {}
1
// Phone.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;
3
4
public class Phone implements Device {}
1
// Watch.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;
3
4
public class Watch implements Device {}
1
// Shape.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;
3
4
public interface Shape {
5
void printTo(Device device);
6
}
1
// Triangle.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;
3
4
public class Triangle implements Shape {
5
6
@Override
7
public void printTo(Device device) {
8
if (device instanceof Phone) {
9
System.out.println("print Triangle to Phone");
10
} else if (device instanceof Watch) {
11
System.out.println("print Triangle to Watch");
12
}
13
}
14
}
1
// Circle.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;
3
4
public class Circle implements Shape {
5
6
@Override
7
public void printTo(Device device) {
8
if (device instanceof Phone) {
9
System.out.println("print Circle to phone");
10
} else if (device instanceof Watch) {
11
System.out.println("print Circle to watch");
12
}
13
}
14
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._01_before;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
Shape rectangle = new Rectangle();
8
Device device = new Phone();
9
rectangle.printTo(device);
10
}
11
}

12.3 예시 - 변경

1
// Device.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public interface Device {
5
void print(Circle circle);
6
7
void print(Rectangle rectangle);
8
9
void print(Triangle triangle);
10
}
1
// Pad.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public class Pad implements Device {
5
6
@Override
7
public void print(Circle circle) {
8
System.out.println("Print Circle to Pad");
9
}
10
11
@Override
12
public void print(Rectangle rectangle) {
13
System.out.println("Print Rectangle to Pad");
14
}
15
16
@Override
17
public void print(Triangle triangle) {
18
System.out.println("Print Triangle to Pad");
19
}
20
}
1
// Phone.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public class Phone implements Device {
5
6
@Override
7
public void print(Circle circle) {
8
System.out.println("Print Circle to Phone");
9
}
10
11
@Override
12
public void print(Rectangle rectangle) {
13
System.out.println("Print Rectangle to Phone");
14
}
15
16
@Override
17
public void print(Triangle triangle) {
18
System.out.println("Print Triangle to Phone");
19
}
20
}
1
// Watch.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public class Watch implements Device {
5
6
@Override
7
public void print(Circle circle) {
8
System.out.println("Print Circle to Watch");
9
}
10
11
@Override
12
public void print(Rectangle rectangle) {
13
System.out.println("Print Rectangle to Watch");
14
}
15
16
@Override
17
public void print(Triangle triangle) {
18
System.out.println("Print Triangle to Watch");
19
}
20
}
1
// Shape.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public interface Shape {
5
void accept(Device device);
6
}
1
// Circle.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public class Circle implements Shape {
5
6
@Override
7
public void accept(Device device) {
8
device.print(this);
9
}
10
}
1
// Triangle.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public class Triangle implements Shape {
5
6
@Override
7
public void accept(Device device) {
8
device.print(this);
9
}
10
}
1
// Rectangle.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public class Rectangle implements Shape {
5
6
@Override
7
public void accept(Device device) {
8
device.print(this);
9
}
10
}
1
// Client.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._02_after;
3
4
public class Client {
5
6
public static void main(String[] args) {
7
Shape rectangle = new Rectangle();
8
Device device = new Pad();
9
rectangle.accept(device);
10
}
11
}

12.4 장단점

  • 장점
    • 기존 코드를 변경하지 않고 새로운 코드를 추가할 수 있다.
    • 추가 기능을 한 곳에 모아둘 수 있다.
  • 단점
    • 복잡하다.
    • 새로운 Element를 추가하거나 제거할 때 모든 Visitor 코드를 변경해야 한다

12.5 실무에서 어떻게 쓰이나?

자바

  • FileVisitor, SimpleFileVisitor
  • AnnotationValueVisitor
  • ElementVisitor
1
// SearchFileVisitor.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._03_java;
3
4
import java.io.IOException;
5
import java.nio.file.*;
6
import java.nio.file.attribute.BasicFileAttributes;
7
8
public class SearchFileVisitor implements FileVisitor<Path> {
9
10
private String fileToSearch;
11
private Path startingDirectory;
12
13
public SearchFileVisitor(String fileToSearch, Path startingDirectory) {
14
this.fileToSearch = fileToSearch;
15
this.startingDirectory = startingDirectory;
16
}
17
18
@Override
19
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
20
throws IOException {
21
return FileVisitResult.CONTINUE;
22
}
23
24
@Override
25
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
26
throws IOException {
27
if (fileToSearch.equals(file.getFileName().toString())) {
28
System.out.println("found " + file.getFileName());
29
return FileVisitResult.TERMINATE;
30
}
31
return FileVisitResult.CONTINUE;
32
}
33
34
@Override
35
public FileVisitResult visitFileFailed(Path file, IOException exc)
36
throws IOException {
37
exc.printStackTrace(System.out);
38
return FileVisitResult.CONTINUE;
39
}
40
41
@Override
42
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
43
throws IOException {
44
if (Files.isSameFile(startingDirectory, dir)) {
45
System.out.println("search end");
46
return FileVisitResult.TERMINATE;
47
}
48
return FileVisitResult.CONTINUE;
49
}
50
}
1
// VisitorInJava.java
2
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._03_java;
3
4
import java.io.IOException;
5
import java.nio.file.Files;
6
import java.nio.file.Path;
7
8
public class VisitorInJava {
9
10
public static void main(String[] args) throws IOException {
11
Path startingDirectory = Path.of("/Users/keesun/workspace/design-patterns");
12
SearchFileVisitor searchFileVisitor = new SearchFileVisitor(
13
"Triangle.java",
14
startingDirectory
15
);
16
Files.walkFileTree(startingDirectory, searchFileVisitor);
17
}
18
}

스프링의 BeanDefinitionVisitor

1
package me.whiteship.designpatterns._03_behavioral_patterns._23_visitor._03_java;
2
3
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
4
5
public class VisitorInSpring {
6
7
public static void main(String[] args) {
8
BeanDefinitionVisitor beanDefinitionVisitor;
9
}
10
}