
java의 Iterator 를 공부하는데 무릎을 탁치게 되었다.
그런데... Iterator 패턴이 있다고 한다..
있었던가.. 그럼 간단하게 패턴을 정리해볼까?
사담
확실히 책으로 읽을때보다 gpt를 사용해서 디자인패턴으로 정리해서 코드로 이해하는게
와 닿는다.... 개발자에게는 gpt는 선생님...
책으로 읽다보면 깊게 깊게 파다보니
요약이 잘 되지 않는다.
그렇게 저는 '또 길을 잃었다.(딴딴딴)👠👠👠'
이미지 출처: https://www.youtube.com/watch?v=An7kqZ5D7j8&list=PLe6NQuuFBu7FhPfxkjDd2cWnTy2y_w_jZ

Gof 뭐여?
-> 4대천왕👑(GoF)이 정리한 패턴!
Gang of Four(4명의 갱)

객체지향 설계 문제를 23개 디자인 패턴을 정의
크게 3개로 나눈다.
1. 생성 패턴 : 객체 생성
2. 구조 패턴: 객체 간 관계
3. 행위 패턴: 객체 간 데이터 주고 받는 방법, 책임 분배
객체 생성할 때 어떻게 만들까?
1. 생성 패턴
싱글톤(Singleton)
원리
- 특정 클래스의 인스턴스를 하나만 생성하고, 어디서든 이를 공유하도록 보장.
- 전역 상태를 관리하거나 공통 리소스를 사용할 때 유용.

class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return instance; }
}
sigleton example code
class Singleton {
private static Singleton instance; // 단일 인스턴스 저장
private Singleton() {} // 생성자를 private으로 막음
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public void showMessage() {
System.out.println("Singleton Instance");
}
}
public class SingletonExample {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
s1.showMessage();
System.out.println("s1과 s2가 같은 인스턴스인가? " + (s1 == s2));
}
}
//결과:
//Singleton Instance
//s1과 s2가 같은 인스턴스인가? true
팩토리 메서드 (Factory Method)
원리
- 객체 생성을 클라이언트 코드에서 분리하여 객체 생성 책임을 팩토리 클래스에 위임.
- 객체 생성을 캡슐화하여 클라이언트는 구체적인 클래스를 몰라도 객체를 생성할 수 있음.
-> 같은 interface를 가진 객체 생성을 담당하는 클래스를 따로 만듦
ShapeFactory class를 따로 만들어서
main에서는 원을 만들지, 사각을 만들지만 정해주면
ShapeFactory 내부에서 잘 정리한 규칙에 따라 객체를 생성함.

interface Product { void use(); }
class ProductA implements Product { public void use() { System.out.println("ProductA"); } }
class Factory {
public Product createProduct() { return new ProductA(); }
}
factory method example code : Shape, Circle, Square
// 제품 인터페이스
interface Shape {
void draw();
}
// 구체적인 제품 클래스들
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Square");
}
}
// 팩토리 클래스
class ShapeFactory {
public Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
public class FactoryExample {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
Shape shape2 = shapeFactory.getShape("SQUARE");
shape2.draw();
}
}
추상 팩토리 (Abstract Factory)
객체를 그룹으로 생성

abstract factory example code:
Button, CheckBox, WindowButton, MacOSButton, GUIFactory, MacFactory, WindowFactory, Application
// 1. 공통 인터페이스
// Abstract Product: Button
interface Button {
void render();
}
// Abstract Product: Checkbox
interface Checkbox {
void render();
}
// 2. 운영체제에 따라 Button과 Checkbox를 구현합니다.
// Concrete Product: Windows Button
class WindowsButton implements Button {
public void render() {
System.out.println("Rendering Windows Button");
}
}
// Concrete Product: MacOS Button
class MacOSButton implements Button {
public void render() {
System.out.println("Rendering MacOS Button");
}
}
// Concrete Product: Windows Checkbox
class WindowsCheckbox implements Checkbox {
public void render() {
System.out.println("Rendering Windows Checkbox");
}
}
// Concrete Product: MacOS Checkbox
class MacOSCheckbox implements Checkbox {
public void render() {
System.out.println("Rendering MacOS Checkbox");
}
}
// 3. 팩토리 인터페이스
// Abstract Factory
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
// 4. 운영체제에 다라 팩토리 구현
// Concrete Factory: Windows Factory
class WindowsFactory implements GUIFactory {
public Button createButton() {
return new WindowsButton();
}
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
// Concrete Factory: MacOS Factory
class MacOSFactory implements GUIFactory {
public Button createButton() {
return new MacOSButton();
}
public Checkbox createCheckbox() {
return new MacOSCheckbox();
}
}
//5. 고객은 구체에 의존하지 않는다
// Client
public class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
// 팩토리를 통해 구체적인 제품 생성
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void render() {
button.render();
checkbox.render();
}
}
// 6. 팩토리 선택
public class Main {
public static void main(String[] args) {
GUIFactory factory;
// 환경 변수나 사용자 입력으로 팩토리를 결정
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
factory = new WindowsFactory();
} else if (os.contains("mac")) {
factory = new MacOSFactory();
} else {
throw new UnsupportedOperationException("Unsupported OS: " + os);
}
// Application 초기화 및 실행
Application app = new Application(factory);
app.render();
}
}
//7. 결과
Windows 환경:
Rendering Windows Button
Rendering Windows Checkbox
MacOS 환경:
Rendering MacOS Button
Rendering MacOS Checkbox
빌더 (Builder)
@Builder 를 적용하면 생기는 코드와 같다.
장점:
생성자처럼 매개변수의 순서를 신경쓸 필요가 없다.
매개변수를 다르게하여, 생성자 오버로딩이 필요하지 않다.
builder instance의 값을 변경하지 않도록 만들 수 있다.
build()하면 new 로 새로운 instance로 전달할 수 있다.
class Product {
String partA, partB;
static class Builder {
Product product = new Product();
Builder setPartA(String a) { product.partA = a; return this; }
Builder setPartB(String b) { product.partB = b; return this; }
Product build() { return product; }
}
}
프로토타입 (Prototype)
객체를 복제하여 생성
class Prototype implements Cloneable {
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
}
객체 간 관계를 어떻게 중재하지? 변경하지?
2. 구조 패턴
어댑터(Adapter)
원리
- 기존 클래스의 인터페이스를 클라이언트에서 원하는 다른 인터페이스로 변환.
- 호환되지 않는 인터페이스를 연결하여 협업을 가능하게 만듦.
class OldSystem { void oldMethod() { System.out.println("Old Method"); } }
interface NewSystem { void newMethod(); }
class Adapter implements NewSystem {
OldSystem oldSystem = new OldSystem();
public void newMethod() { oldSystem.oldMethod(); }
}
adapter example code: OldPrinter, Printer, PrinterAdapter,
// 기존 클래스
class OldPrinter {
public void printOldMessage() {
System.out.println("Printing using the old printer.");
}
}
// 새로운 인터페이스
interface Printer {
void print();
}
// 어댑터 클래스
class PrinterAdapter implements Printer {
private OldPrinter oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter) {
this.oldPrinter = oldPrinter;
}
@Override
public void print() {
oldPrinter.printOldMessage(); // 기존 클래스 호출
}
}
public class AdapterExample {
public static void main(String[] args) {
OldPrinter oldPrinter = new OldPrinter();
Printer adapter = new PrinterAdapter(oldPrinter);
adapter.print(); // 새 인터페이스를 사용해 호출
}
}
결과:
Printing using the old printer.
브리지 (Bridge)
구현부와 추상부 분리
interface Device { void turnOn(); }
class TV implements Device { public void turnOn() { System.out.println("TV On"); } }
class Remote {
Device device;
Remote(Device device) { this.device = device; }
void toggle() { device.turnOn(); }
}
컴포지트 (Composite)
트리구조 표현
Interface
Leaf : 가장 마지막 단위
Composite: 부모
interface Component { void show(); }
class Leaf implements Component { public void show() { System.out.println("Leaf"); } }
class Composite implements Component {
List<Component> children = new ArrayList<>();
public void show() { children.forEach(Component::show); }
}
composite example code:
FileSystemComponent, File, Directory
// 1. 인터페이스
import java.util.List;
interface FileSystemComponent {
void display(String indent); // 계층 구조를 표시하는 메서드
}
// 2. Leaf
class File implements FileSystemComponent {
private String name;
public File(String name) {
this.name = name;
}
@Override
public void display(String indent) {
System.out.println(indent + "File: " + name);
}
}
// 3. Parent
import java.util.ArrayList;
import java.util.List;
class Directory implements FileSystemComponent {
private String name;
private List<FileSystemComponent> children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void add(FileSystemComponent component) {
children.add(component);
}
public void remove(FileSystemComponent component) {
children.remove(component);
}
@Override
public void display(String indent) {
System.out.println(indent + "Directory: " + name);
for (FileSystemComponent child : children) {
child.display(indent + " "); // 들여쓰기 추가
}
}
}
// 4. 사용
public class Main {
public static void main(String[] args) {
// Leaf 객체 생성 (파일)
File file1 = new File("file1.txt");
File file2 = new File("file2.txt");
File file3 = new File("file3.txt");
// Composite 객체 생성 (디렉토리)
Directory rootDirectory = new Directory("root");
Directory subDirectory1 = new Directory("subdir1");
Directory subDirectory2 = new Directory("subdir2");
// 트리 구조 구성
rootDirectory.add(file1);
rootDirectory.add(subDirectory1);
subDirectory1.add(file2);
subDirectory1.add(subDirectory2);
subDirectory2.add(file3);
// 트리 구조 출력
rootDirectory.display("");
}
}
// 5. 결과
Directory: root
File: file1.txt
Directory: subdir1
File: file2.txt
Directory: subdir2
File: file3.txt
퍼사드 (Facade)
복잡한 시스템에 단순한 인터페이스 제공
데코레이터보다 더 간단하게 기능 추가 가능?
class Subsystem { void operation() { System.out.println("Subsystem Operation"); } }
class Facade {
Subsystem subsystem = new Subsystem();
void simpleOperation() { subsystem.operation(); }
}
플라이웨이트 (Flyweight)
공유 가능한 객체를 재사용
map을 사용해서 객체를 생성하지 못하게 하네
class FlyweightFactory {
private static final Map<String, Flyweight> cache = new HashMap<>();
public static Flyweight get(String key) {
return cache.computeIfAbsent(key, k -> new Flyweight(key));
}
}
class Flyweight { String data; Flyweight(String data) { this.data = data; } }
프록시 (Proxy)
template method, strategy pattern은 결국 코드를 많이 수정해야한다.
원본을 수정하지 않고 어떻게 개선할 수 있을까?
interface Subject { void request(); }
class RealSubject implements Service { public void request() { System.out.println("Real Service"); } }
class Proxy implements Service {
Subject subject = new RealSubject();
public void request() { System.out.println("Proxy Access"); subject.request(); }
}
RealSubject 를 직접 호출하지 않고,
Proxy 클래스의 request()를 호출하면 RealSubject의 request()를 호출한다.
접근제어를 위한 대리자
대리자는 요청온 것을 감시, 수정해서 목적지에 전달 가능하다.

프록시의 주요 기능
프록시를 통해서 할 수 있는 일은 크게 2가지로 구분할 수 있다.
- 접근 제어
- 권한에 따른 접근 차단
- 캐싱
- 지연 로딩
- 부가 기능 추가
- 원래 서버가 제공하는 기능에 더해서 부가 기능을 수행한다.
- 예) 요청 값이나, 응답 값을 중간에 변형한다.
- 예) 실행 시간을 측정해서 추가 로그를 남긴다.
장점
- client 코드의 변경 없이 자유롭게 프록시를 넣고 뺄 수 있다. 실제 client 입장에서는 프록시 객체가 주입되었는지, 실제 객체가 주입되었는지 알지 못한다.
- 객체 생성과 초기화 과정을 효율적으로 관리.
- 접근 제어 및 추가 기능(로깅, 캐싱 등) 구현 가능.
- 클라이언트 코드와 실제 객체의 구체적인 구현을 분리.
단점
- 설계가 복잡해질 수 있음.
- 프록시를 추가하면 객체 호출의 간접적인 오버헤드가 발생.
프록시 패턴의 활용 사례
- 가상 프록시 (Virtual Proxy):
- 리소스가 큰 객체를 지연 초기화하거나, 필요할 때만 로드하는 데 사용.
- 예: 대형 이미지, 동영상, 데이터베이스 연결.
- 보호 프록시 (Protection Proxy):
- 접근 제어를 위해 사용.
- 예: 권한에 따라 객체 접근을 제한.
- 원격 프록시 (Remote Proxy):
- 원격 객체(네트워크 상의 객체)를 로컬에서 대리하여 호출.
- 예: RMI(Remote Method Invocation).
- 스마트 프록시 (Smart Proxy):
- 추가 작업(로깅, 캐싱 등)을 수행.
- 예: 메서드 호출 로깅, 트랜잭션 관리.

proxy example code: cache 적용
proxy 도입 전 - cache 적용 안됨
package hello.proxy.pureproxy.proxy.code;
public interface Subject {
String operation();
}
package hello.proxy.pureproxy.proxy.code;
import lombok.extern.slf4j.Slf4j;
import javax.security.auth.Subject;
@Slf4j
public class RealSubject implements Subject {
@Override
public String operation() {
log.info("실제 객체 호출");
sleep(1000);
return "data";
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package hello.proxy.pureproxy.proxy.code;
import javax.security.auth.Subject;
public class ProxyPatternClient {
private Subject subject;
public ProxyPatternClient(Subject subject) {
this.subject = subject;
}
public void execute() {
subject.operation();
}
}
package hello.proxy.pureproxy.proxy;
import hello.proxy.pureproxy.proxy.code.ProxyPatternClient;
import hello.proxy.pureproxy.proxy.code.RealSubject;
import org.junit.jupiter.api.Test;
public class ProxyPatternTest {
@Test
void noProxyTest() {
RealSubject realSubject = new RealSubject();
ProxyPatternClient client = new ProxyPatternClient(realSubject);
client.execute();
client.execute();
client.execute();
}
}
// 결과
RealSubject - 실제 객체 호출
RealSubject - 실제 객체 호출
RealSubject - 실제 객체 호출
실제 객체를 3번 호출한다.
proxy 도입 후 - cache 적용
package hello.proxy.pureproxy.proxy.code;
import lombok.extern.slf4j.Slf4j;
import javax.security.auth.Subject;
@Slf4j
public class CacheProxy implements Subject { // 캐시 구체 class
private Subject target;// 기존에는 애만 있었음
private String cacheValue;// 캐시 값
public CacheProxy(Subject target) {
this.target = target;
}
@Override
public String operation() {
log.info("프록시 호출");
if (cacheValue == null) {// 없으면 진짜 객체를 호출해, 이미 있으면 캐시값 쓰렴
cacheValue = target.operation();
}
return cacheValue;
}
}
package hello.proxy.pureproxy.proxy;
import hello.proxy.pureproxy.proxy.code.CacheProxy;
import hello.proxy.pureproxy.proxy.code.ProxyPatternClient;
import hello.proxy.pureproxy.proxy.code.RealSubject;
import hello.proxy.pureproxy.proxy.code.Subject;
import org.junit.jupiter.api.Test;
public class ProxyPatternTest {
@Test
void noProxyTest() {
RealSubject realSubject = new RealSubject();
ProxyPatternClient client = new ProxyPatternClient(realSubject);
client.execute();
client.execute();
client.execute();
}
@Test
void cacheProxyTest() {
Subject realSubject = new RealSubject();
Subject cacheProxy = new CacheProxy(realSubject);
ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
client.execute();
client.execute();
client.execute();
}
}
// 결과
CacheProxy - 프록시 호출
RealSubject - 실제 객체 호출
CacheProxy - 프록시 호출
CacheProxy - 프록시 호출
처음 null일때만 실제 객체를 호출하고 나머지는 캐시에 있는 값을 가져다가 쓴다.
proxy example code: Image, RealImage, ProxyImage
// 1. 인터페이스
interface Image {
void display(); // 이미지를 출력하는 메서드
}
// 2. 작업할 객체
// RealSubject: 실제 객체
class RealImage implements Image {
private String fileName;//공통
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(); // 이미지 로드
}
private void loadFromDisk() {
System.out.println("Loading image from disk: " + fileName);
}
@Override
public void display() {
System.out.println("Displaying image: " + fileName);
}
}
// 3. 대리자
// Proxy: 대리 객체
class ProxyImage implements Image {
private String fileName;//공통
private RealImage realImage;//프록시에서 추가
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
// 실제 객체를 필요한 시점에 생성
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// 4. 사용
public class Main {
public static void main(String[] args) {
// ProxyImage 객체 생성
Image image1 = new ProxyImage("photo1.jpg");
Image image2 = new ProxyImage("photo2.jpg");
// 이미지 로드 없이 호출
System.out.println("Before displaying images...");
// 첫 번째 이미지를 표시 (이 시점에 실제 객체 생성)
image1.display();
System.out.println();
// 두 번째 이미지를 표시 (이 시점에 실제 객체 생성)
image2.display();
System.out.println();
// 다시 첫 번째 이미지를 표시 (이미 생성된 객체 사용)
image1.display();
}
}
// 5. 결과
Before displaying images...
Loading image from disk: photo1.jpg
Displaying image: photo1.jpg
Loading image from disk: photo2.jpg
Displaying image: photo2.jpg
Displaying image: photo1.jpg
설명
- 지연 초기화:
- ProxyImage는 실제 객체(RealImage)를 즉시 생성하지 않고, display() 메서드가 호출될 때 생성합니다.
- 불필요한 리소스 사용을 방지합니다.
- 프록시와 실제 객체의 구분:
- 클라이언트는 ProxyImage와 RealImage를 구분하지 않고 동일한 인터페이스(Image)를 사용합니다.
- 이는 클라이언트 코드와 실제 구현의 분리를 가능하게 합니다.
- 캐싱:
- 이미 생성된 객체는 재사용됩니다.
데코레이터 (Decorator)
객체에 동적으로 기능을 추가, 추가, 추가..
의도
- 프록시 패턴의 의도: 다른 개체에 대한 **접근을 제어**하기 위해 대리자를 제공
- 데코레이터 패턴의 의도: **객체에 추가 책임(기능)을 동적으로 추가**하고, 기능 확장을 위한 유연한 대안 제공
예시
- 요청 값이나, 응답 값을 중간에 변형
- 실행 시간을 측정해서 추가 로그를 남긴다.


decorator example code: Component, ConcreateComponent, Decorator
// Component 인터페이스
interface Component {
String operation();
}
// Concrete Component
class ConcreteComponent implements Component {
public String operation() {
return "Base Component";
}
}
// Decorator
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public String operation() {
return component.operation();
}
}
// Concrete Decorators
class DecoratorA extends Decorator {
public DecoratorA(Component component) {
super(component);
}
public String operation() {
return super.operation() + " + DecoratorA";
}
}
class DecoratorB extends Decorator {
public DecoratorB(Component component) {
super(component);
}
public String operation() {
return super.operation() + " + DecoratorB";
}
}
public class Main {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decorated = new DecoratorA(new DecoratorB(component));
System.out.println(decorated.operation());
}
}
객체가 변경됐네? 여러 객체한테 데이터 변경하라고 알리자!
3. 행위 패턴
옵저버(Observer)
원리
- 객체의 상태가 변경되면 이를 의존하고 있는 다른 객체(옵저버)들에게 자동으로 알림.
- 이벤트 기반 시스템에서 자주 사용.

observer example code: Observer, Subject, ConcreateObserver
import java.util.ArrayList;
import java.util.List;
// 옵저버 인터페이스
interface Observer {
void update(String message);
}
// 주제 클래스(Subject)
class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// 구체적인 옵저버 클래스
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println("Observer " + name + " received message: " + message);
}
}
public class ObserverExample {
public static void main(String[] args) {
Subject subject = new Subject();
Observer observer1 = new ConcreteObserver("A");
Observer observer2 = new ConcreteObserver("B");
subject.attach(observer1);
subject.attach(observer2);
subject.notifyObservers("State has changed!");
}
}
결과:
Observer A received message: State has changed!
Observer B received message: State has changed!
책임 연쇄 (Chain of Responsibility)
요청을 여러 객체가 처리
abstract class Handler {
Handler next;
void setNext(Handler next) { this.next = next; }
abstract void handle(String request);
}
class ConcreteHandler extends Handler {
void handle(String request) { System.out.println("Handled: " + request); }
}
커맨드 (Command)
요청을 캡슐화
interface Command { void execute(); }
class LightOnCommand implements Command {
public void execute() { System.out.println("Light On"); }
}
인터프리터 (Interpreter)
해석자, 변역자
interface Expression { boolean interpret(String context); }
class TerminalExpression implements Expression {
private String data;
TerminalExpression(String data) { this.data = data; }
public boolean interpret(String context) { return context.contains(data); }
}
반복자 (Iterator)
컬렉션 순회
interface Iterator { boolean hasNext(); Object next(); }
class ConcreteIterator implements Iterator {
int index = 0; String[] data = {"A", "B"};
public boolean hasNext() { return index < data.length; }
public Object next() { return data[index++]; }
}
Iterator 인터페이스는 hasNext(), Object next() 을 무조건 구현하라고 한다.
ConcreteIterator 구현 클래스는 hasNext(), Object next() 를 구현해놨다.
main에서 ConcreteIterator 객체는 hasNext(), next() 함수로 순회가 가능하다.
ConcreteIterator의 자료구조는 문자 배열이다.
Iterable 인터페이스로 인해 Collection의 하위인 List, Set, Queue가 같은 함수 hasNext(), next()로 호출이 가능해진다

중재자 (Mediator)
객체 간 통신을 중재한다
interface Mediator { void notify(String event); }
class ConcreteMediator implements Mediator {
public void notify(String event) { System.out.println("Event: " + event); }
}
상태 (State)
상태에 따라 행동을 변경한다
interface State { void handle(); }
class ConcreteState implements State {
public void handle() { System.out.println("Handling State"); }
}
state example code: PaymentState, PendingState, ProcessingState, CompleteState, FailedState, PaymentContext
// State 인터페이스
public interface PaymentState {
void processPayment(PaymentContext context);
void cancelPayment(PaymentContext context);
}
// ConcreteState 1: 대기 상태
public class PendingState implements PaymentState {
@Override
public void processPayment(PaymentContext context) {
System.out.println("결제를 진행 중입니다...");
context.setState(new ProcessingState()); // 상태 전환
}
@Override
public void cancelPayment(PaymentContext context) {
System.out.println("대기 상태에서 결제가 취소되었습니다.");
context.setState(new FailedState()); // 상태 전환
}
}
// ConcreteState 2: 진행 상태
public class ProcessingState implements PaymentState {
@Override
public void processPayment(PaymentContext context) {
System.out.println("결제가 이미 진행 중입니다. 완료를 기다리세요.");
}
@Override
public void cancelPayment(PaymentContext context) {
System.out.println("진행 상태에서 결제가 취소되었습니다.");
context.setState(new FailedState()); // 상태 전환
}
}
// ConcreteState 3: 완료 상태
public class CompletedState implements PaymentState {
@Override
public void processPayment(PaymentContext context) {
System.out.println("결제가 이미 완료되었습니다.");
}
@Override
public void cancelPayment(PaymentContext context) {
System.out.println("완료된 결제는 취소할 수 없습니다.");
}
}
// ConcreteState 4: 실패 상태
public class FailedState implements PaymentState {
@Override
public void processPayment(PaymentContext context) {
System.out.println("결제가 실패했습니다. 다시 시도하세요.");
context.setState(new PendingState()); // 상태 전환
}
@Override
public void cancelPayment(PaymentContext context) {
System.out.println("결제가 이미 실패한 상태입니다.");
}
}
// Context 클래스
public class PaymentContext {
private PaymentState state;
public PaymentContext() {
// 초기 상태 설정 (대기 상태)
this.state = new PendingState();
}
public void setState(PaymentState state) {
this.state = state;
}
public void processPayment() {
state.processPayment(this);
}
public void cancelPayment() {
state.cancelPayment(this);
}
}
// 테스트 클래스
public class Main {
public static void main(String[] args) {
PaymentContext payment = new PaymentContext();
// 결제 진행
payment.processPayment(); // "결제를 진행 중입니다..."
payment.processPayment(); // "결제가 이미 진행 중입니다."
// 결제 취소
payment.cancelPayment(); // "진행 상태에서 결제가 취소되었습니다."
// 결제 재시도
payment.processPayment(); // "결제를 진행 중입니다..."
payment.processPayment(); // "결제가 이미 진행 중입니다."
}
}
템플릿 메서드 (Template Method)
![]() |
![]() |
변하는 것, 변하지 않는 것 분리
- 변하는 것: 핵심 기능
- 변하지 않는 것: 템플릿
핵심기능 vs 부가 기능
- 핵심 기능: 해당 객체가 제공하는 고유 기능.
- 부가 기능: 핵심 기능을 보조하기 위해 제공하는 기능.
- ex) log 추적기, transaction
다형성, 상속 사용
- abstract class에 공통 기능을 넣어둔다.
- 자식은 override하여 핵심기능을 수정한다.
SRP
단일 책임 원칙을 준수하고 있다.
각 클래스가 명확한 책임을 가짐
- 상위 클래스(Template): 알고리즘의 구조를 정의.
- 하위 클래스(ConcreteClass): 알고리즘의 구체적인 단계 구현.
이로 인해 한 클래스가 지나치게 많은 책임을 지는 것을 방지합니다.

abstract class == 상속 --> overrride 사용
- abstract class에서 알고리즘의 골격(템플릿)을 정의
- 세부적인 구현은 하위 클래스에서 재정의(override)
- 코드 재사용성을 높이고, 알고리즘의 구조를 유지하면서 세부 구현의 유연성
단점:
- 상속
- 자식은 부모의 기능이 다 필요없는데도, 가지고 있어야한다.
- 자식은 부모 클래스를 강하게 의존
해결책:
- template method pattern과 비슷한 역할을 하면서 상속의 단점을 제거한 것이 strategy pattern이다.
template method example code : CookingTemplate, Pasta, BBQ

// 1. 골격 정의
abstract class CookingTemplate {
// 템플릿 메서드: 요리 과정의 전체 흐름을 정의
public final void cook() {
prepareIngredients();
cookDish();
serveDish();
}
// 공통 단계 (구현 제공)
private void prepareIngredients() {
System.out.println("재료를 준비합니다.");
}
// 추상 메서드: 하위 클래스에서 구체적인 구현 제공
protected abstract void cookDish();
// 공통 단계 (구현 제공)
private void serveDish() {
System.out.println("요리를 접시에 담아 제공합니다.");
}
}
// 2. 구체 정의
class PastaCooking extends CookingTemplate {
@Override
protected void cookDish() {
System.out.println("파스타를 조리합니다.");
}
}
class KoreanBBQCooking extends CookingTemplate {
@Override
protected void cookDish() {
System.out.println("고기를 구워 한국식 바비큐를 준비합니다.");
}
}
// 3. 실행
public class Main {
public static void main(String[] args) {
// 파스타 요리
CookingTemplate pastaCooking = new PastaCooking();
System.out.println("== 파스타 요리 시작 ==");
pastaCooking.cook();
System.out.println();
// 한국식 바비큐 요리
CookingTemplate koreanBBQCooking = new KoreanBBQCooking();
System.out.println("== 한국식 바비큐 요리 시작 ==");
koreanBBQCooking.cook();
}
}
//4. 결과
== 파스타 요리 시작 ==
재료를 준비합니다.
파스타를 조리합니다.
요리를 접시에 담아 제공합니다.
== 한국식 바비큐 요리 시작 ==
재료를 준비합니다.
고기를 구워 한국식 바비큐를 준비합니다.
요리를 접시에 담아 제공합니다.
전략 (Strategy)
- template method pattern의 상속의 단점을 제거함
- 위임(=역할, interface)을 정의하고, 대상을 갈아끼움
- 캡슐화 사용
변하는 것, 변하지 않는 것
- 변하는 것: 핵심 기능, 회색
- 변하지 않는 것: 템플릿, 초록색

여기서 한번 더 합칠 수 있다.
Context class에서 setStrategy(Strategy st)와 executeStrategy(int a, int b) 를 하나로 할수 있다.
한계
아무리 최적해도, 공통 부분을 적용하기 위해 원본 코드를 수정해야한다.
class가 수백개이면 수백개를 수정해야한다.
그래서 나온 것이 프록시 패턴
strategy example code: Strategy, AddStrategy, MultiplyStrategy, Context
// 전략 인터페이스
interface Strategy {
int execute(int a, int b);
}
// 구체적인 전략
class AddStrategy implements Strategy {
public int execute(int a, int b) {
return a + b;
}
}
class MultiplyStrategy implements Strategy {
public int execute(int a, int b) {
return a * b;
}
}
// 컨텍스트 클래스
class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
return strategy.execute(a, b);
}
}
public class Main {
public static void main(String[] args) {
Context context = new Context();
context.setStrategy(new AddStrategy());
System.out.println("Addition: " + context.executeStrategy(5, 3));
context.setStrategy(new MultiplyStrategy());
System.out.println("Multiplication: " + context.executeStrategy(5, 3));
}
}
방문자 (Visitor)
객체 구조를 순회하며 작업 수행
interface Visitor { void visit(Element element); }
class Element {
void accept(Visitor visitor) { visitor.visit(this); }
}
'기술 블로그 (Tech Blog) > Dev Notes' 카테고리의 다른 글
singleton - Problem&Solve (0) | 2024.12.22 |
---|---|
자료구조 정리 (0) | 2024.12.15 |
사가패턴은 왜 사가라고 하는걸까? 🍎아님 (0) | 2024.11.16 |