생성 패턴 (Creational Patterns) - 5개
Singleton - "오직 하나만"
java
public class Database {
private static Database instance;
private Database() {} // private 생성자로 외부 생성 차단
public static Database getInstance() {
if (instance == null) {
instance = new Database();
}
return instance;
}
}
실제 사용 예: 데이터베이스 커넥션 풀, 로거, 캐시 매니저. 전역적으로 하나의 인스턴스만 필요한 경우에 사용.
Factory Method - "어떤 클래스를 만들지는 서브클래스가 결정"
java
abstract class TransportFactory {
abstract Transport createTransport();
public void deliver() {
Transport transport = createTransport(); // 구체적인 운송수단은 서브클래스가 결정
transport.deliver();
}
}
class TruckFactory extends TransportFactory {
Transport createTransport() { return new Truck(); }
}
실제 사용 예: UI 컴포넌트 생성(윈도우/맥 버튼), 데이터베이스 드라이버 선택, 로그 출력 방식 선택
Abstract Factory - "관련된 객체들의 세트를 한번에"
java
interface GUIFactory {
Button createButton();
TextField createTextField();
Checkbox createCheckbox();
}
class WindowsFactory implements GUIFactory {
Button createButton() { return new WindowsButton(); }
TextField createTextField() { return new WindowsTextField(); }
Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
실제 사용 예: 게임에서 테마별 UI 세트, 다양한 OS용 컴포넌트 생성, 데이터베이스별 DAO 팩토리
Builder - "복잡한 객체를 단계별로 조립"
java
class Computer {
private String CPU, RAM, storage, GPU;
static class ComputerBuilder {
private Computer computer = new Computer();
ComputerBuilder setCPU(String cpu) { computer.CPU = cpu; return this; }
ComputerBuilder setRAM(String ram) { computer.RAM = ram; return this; }
ComputerBuilder setStorage(String storage) { computer.storage = storage; return this; }
Computer build() { return computer; }
}
}
Computer computer = new Computer.ComputerBuilder()
.setCPU("Intel i7")
.setRAM("16GB")
.setStorage("1TB SSD")
.build();
실제 사용 예: SQL 쿼리 빌더, 복잡한 설정 객체 생성, HTTP 요청 빌더
Prototype - "복사해서 새로 만들기"
java
abstract class Shape implements Cloneable {
protected String type;
abstract void draw();
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
// 사용: 기존 도형을 복제해서 새 도형 생성
Shape clonedShape = (Shape) originalShape.clone();
실제 사용 예: 게임에서 몬스터/아이템 복제, 문서 템플릿 복사, 복잡한 설정 객체 복제
구조 패턴 (Structural Patterns) 상세 설명
1. Adapter - "다른 규격을 맞춰주는 어댑터"
핵심 개념: 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동할 수 있게 해주는 패턴
실생활 비유:
- 한국의 220V 플러그를 미국의 110V 콘센트에 꽂을 때 사용하는 어댑터
- USB-C를 USB-A로 변환해주는 어댑터
java
// 기존에 있던 레거시 시스템 (수정할 수 없음)
class LegacyPaymentSystem {
void makePayment(String amount, String currency) {
System.out.println("Legacy: 결제 " + amount + " " + currency);
}
}
// 새로운 시스템에서 기대하는 인터페이스
interface ModernPaymentInterface {
void pay(double amount);
}
// 어댑터: 레거시 시스템을 새로운 인터페이스에 맞춤
class PaymentAdapter implements ModernPaymentInterface {
private LegacyPaymentSystem legacySystem;
public PaymentAdapter(LegacyPaymentSystem legacySystem) {
this.legacySystem = legacySystem;
}
@Override
public void pay(double amount) {
// 새로운 형식을 레거시 형식으로 변환
legacySystem.makePayment(String.valueOf(amount), "KRW");
}
}
// 사용
LegacyPaymentSystem legacy = new LegacyPaymentSystem();
ModernPaymentInterface payment = new PaymentAdapter(legacy);
payment.pay(1000.0); // 새로운 방식으로 호출하지만 내부적으로는 레거시 방식 사용
언제 사용하나?
- 기존 라이브러리를 새 프로젝트에 통합할 때
- 레거시 코드를 건드리지 않고 새 시스템과 연동할 때
- 서드파티 API를 내 프로젝트의 인터페이스에 맞출 때
2. Bridge - "추상화와 구현을 분리"
핵심 개념: 구현부와 추상부를 분리해서 둘 다 독립적으로 변화할 수 있게 하는 패턴
실생활 비유:
- TV 리모컨(추상)과 TV 본체(구현)의 관계
- 리모컨은 하나지만 삼성TV, LG TV, 소니TV 등 다양한 TV를 조작 가능
java
// 구현부 인터페이스 (실제 작업을 수행)
interface DrawingAPI {
void drawCircle(int x, int y, int radius);
void drawRectangle(int x, int y, int width, int height);
}
// 구체적인 구현 1: Windows에서 그리기
class WindowsDrawingAPI implements DrawingAPI {
public void drawCircle(int x, int y, int radius) {
System.out.println("Windows API로 원 그리기: (" + x + "," + y + ") 반지름:" + radius);
}
public void drawRectangle(int x, int y, int width, int height) {
System.out.println("Windows API로 사각형 그리기");
}
}
// 구체적인 구현 2: Linux에서 그리기
class LinuxDrawingAPI implements DrawingAPI {
public void drawCircle(int x, int y, int radius) {
System.out.println("Linux X11로 원 그리기: (" + x + "," + y + ") 반지름:" + radius);
}
public void drawRectangle(int x, int y, int width, int height) {
System.out.println("Linux X11로 사각형 그리기");
}
}
// 추상부 (Bridge 역할)
abstract class Shape {
protected DrawingAPI drawingAPI; // 구현부와 연결하는 다리
protected Shape(DrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
abstract void draw();
}
// 구체적인 추상부
class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x; this.y = y; this.radius = radius;
}
public void draw() {
drawingAPI.drawCircle(x, y, radius); // 실제 그리기는 구현부에 위임
}
}
// 사용
DrawingAPI windowsAPI = new WindowsDrawingAPI();
DrawingAPI linuxAPI = new LinuxDrawingAPI();
Shape circle1 = new Circle(5, 10, 3, windowsAPI); // Windows에서 그릴 원
Shape circle2 = new Circle(5, 10, 3, linuxAPI); // Linux에서 그릴 원
circle1.draw(); // Windows API로 원 그리기
circle2.draw(); // Linux X11로 원 그리기
왜 좋은가?
- 플랫폼이 바뀌어도 Shape 코드는 변경 불필요
- 새로운 도형 추가 시 DrawingAPI는 건드리지 않음
- 새로운 플랫폼 추가 시 Shape는 건드리지 않음
언제 사용하나?
- 크로스 플랫폼 애플리케이션 개발
- 데이터베이스 추상화 (MySQL, PostgreSQL, Oracle 등을 같은 인터페이스로)
- 여러 구현체를 런타임에 교체해야 할 때
3. Composite - "개별 객체와 복합 객체를 동일하게 처리"
핵심 개념: 트리 구조로 객체들을 구성하여 부분-전체 계층을 표현하는 패턴
실생활 비유:
- 파일 시스템: 파일과 폴더를 동일하게 처리 (폴더 안에 파일/폴더가 들어있음)
- 회사 조직도: 개인과 팀을 동일하게 처리 (팀 안에 개인/하위팀이 들어있음)
java
// 공통 인터페이스
interface FileSystemItem {
void showDetails();
int getSize();
}
// 개별 객체 (Leaf): 파일
class File implements FileSystemItem {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public void showDetails() {
System.out.println("파일: " + name + " (" + size + "KB)");
}
public int getSize() {
return size;
}
}
// 복합 객체 (Composite): 폴더
class Folder implements FileSystemItem {
private String name;
private List<FileSystemItem> items = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileSystemItem item) {
items.add(item);
}
public void remove(FileSystemItem item) {
items.remove(item);
}
public void showDetails() {
System.out.println("폴더: " + name + " (전체 크기: " + getSize() + "KB)");
// 하위 항목들도 모두 보여주기
for (FileSystemItem item : items) {
System.out.print(" "); // 들여쓰기
item.showDetails();
}
}
public int getSize() {
int totalSize = 0;
for (FileSystemItem item : items) {
totalSize += item.getSize(); // 모든 하위 항목의 크기 합계
}
return totalSize;
}
}
// 사용
Folder documents = new Folder("Documents");
documents.add(new File("resume.pdf", 150));
documents.add(new File("photo.jpg", 300));
Folder projects = new Folder("Projects");
projects.add(new File("code.java", 50));
projects.add(new File("readme.md", 5));
documents.add(projects); // 폴더 안에 폴더 추가
// 개별 파일과 전체 폴더를 동일하게 처리
documents.showDetails();
/*
출력:
폴더: Documents (전체 크기: 505KB)
파일: resume.pdf (150KB)
파일: photo.jpg (300KB)
폴더: Projects (전체 크기: 55KB)
파일: code.java (50KB)
파일: readme.md (5KB)
*/
언제 사용하나?
- 트리 구조의 데이터를 다룰 때
- 개별 객체와 객체 그룹을 동일하게 처리하고 싶을 때
- GUI 컴포넌트 (버튼, 패널, 윈도우를 모두 Component로 처리)
4. Decorator - "기능을 덧붙이는 랩핑지"
핵심 개념: 객체에 동적으로 새로운 기능을 추가할 수 있는 패턴
실생활 비유:
- 햄버거에 치즈, 베이컨, 양상추 등을 추가하는 것
- 선물을 포장지, 리본, 카드 등으로 꾸미는 것
java
// 기본 인터페이스
interface Coffee {
String getDescription();
double getCost();
}
// 기본 커피 (장식될 대상)
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
// 데코레이터 기본 클래스
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee; // 장식할 객체
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
public String getDescription() {
return coffee.getDescription();
}
public double getCost() {
return coffee.getCost();
}
}
// 구체적인 데코레이터들
class Milk extends CoffeeDecorator {
public Milk(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}
class Sugar extends CoffeeDecorator {
public Sugar(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
public double getCost() {
return coffee.getCost() + 0.3;
}
}
class Whip extends CoffeeDecorator {
public Whip(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Whip";
}
public double getCost() {
return coffee.getCost() + 0.8;
}
}
// 사용 - 여러 장식을 중첩해서 적용
Coffee myCoffee = new SimpleCoffee();
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee = $2.0"
myCoffee = new Milk(myCoffee);
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee, Milk = $2.5"
myCoffee = new Sugar(myCoffee);
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee, Milk, Sugar = $2.8"
myCoffee = new Whip(myCoffee);
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee, Milk, Sugar, Whip = $3.6"
상속과의 차이점:
- 상속: 컴파일 타임에 기능이 정해짐 (MilkSugarCoffee 클래스를 미리 만들어야 함)
- 데코레이터: 런타임에 동적으로 기능 조합 가능
언제 사용하나?
- 객체에 기능을 동적으로 추가/제거하고 싶을 때
- 상속으로는 너무 많은 클래스가 생길 때 (조합 폭발)
- Java의 InputStream/OutputStream 계층
5. Facade - "복잡한 것을 간단하게 보이는 창구"
핵심 개념: 복잡한 서브시스템에 대한 간단한 인터페이스를 제공하는 패턴
실생활 비유:
- 은행 창구 직원 (복잡한 내부 업무를 대신 처리해줌)
- 리셉셔니스트 (호텔의 복잡한 서비스들을 간단히 요청할 수 있게 해줌)
java
// 복잡한 서브시스템들
class CPU {
public void freeze() { System.out.println("CPU 정지"); }
public void jump(long position) { System.out.println("CPU 점프 to " + position); }
public void execute() { System.out.println("CPU 실행 시작"); }
}
class Memory {
public void load(long position, byte[] data) {
System.out.println("메모리 로드: " + data.length + " bytes at " + position);
}
}
class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("하드디스크에서 " + size + " bytes 읽기");
return new byte[size];
}
}
class SoundCard {
public void beep() { System.out.println("부팅음 재생"); }
}
// Facade 클래스 - 복잡한 부팅 과정을 간단하게 만듦
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
private SoundCard soundCard;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
this.soundCard = new SoundCard();
}
// 복잡한 부팅 절차를 하나의 간단한 메서드로
public void startComputer() {
System.out.println("컴퓨터 부팅 시작...");
cpu.freeze();
memory.load(0, hardDrive.read(0, 1024));
cpu.jump(0);
cpu.execute();
soundCard.beep();
System.out.println("부팅 완료!");
}
public void shutdownComputer() {
System.out.println("컴퓨터 종료 중...");
// 복잡한 종료 절차들...
System.out.println("종료 완료!");
}
}
// 사용 - 복잡한 내부 과정을 몰라도 됨
ComputerFacade computer = new ComputerFacade();
computer.startComputer(); // 간단한 호출로 복잡한 작업 수행
computer.shutdownComputer();
왜 좋은가?
- 클라이언트는 복잡한 내부 구조를 알 필요 없음
- 서브시스템의 변경이 클라이언트에 영향을 주지 않음
- 자주 사용되는 복잡한 작업을 간단하게 만들 수 있음
언제 사용하나?
- 복잡한 라이브러리나 API를 간단하게 사용하고 싶을 때
- 레거시 시스템을 현대적인 인터페이스로 감싸고 싶을 때
6. Flyweight - "많은 객체를 효율적으로 관리"
핵심 개념: 많은 수의 객체를 효율적으로 지원하기 위해 공통 데이터를 공유하는 패턴
실생활 비유:
- 도서관의 책들 (같은 책을 여러 사람이 빌려볼 수 있지만 책 자체는 하나)
- 워드 프로세서의 문자들 (같은 글꼴/크기의 'A'는 하나만 만들고 위치만 다르게)
java
// 내재적 상태 (Intrinsic State) - 공유되는 부분
class TreeType {
private String name; // 나무 종류
private String color; // 색상
private String sprite; // 이미지
public TreeType(String name, String color, String sprite) {
this.name = name;
this.color = color;
this.sprite = sprite;
}
// 외재적 상태(Extrinsic State)를 매개변수로 받아서 렌더링
public void render(Canvas canvas, int x, int y, int size) {
System.out.println("렌더링: " + name + "(" + color + ") at (" + x + "," + y + ") size:" + size);
// 실제로는 canvas에 sprite를 그림
}
}
// Flyweight Factory - 공유 객체들을 관리
class TreeTypeFactory {
private static Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color, String sprite) {
String key = name + "-" + color + "-" + sprite;
TreeType treeType = treeTypes.get(key);
if (treeType == null) {
System.out.println("새로운 TreeType 생성: " + key);
treeType = new TreeType(name, color, sprite);
treeTypes.put(key, treeType);
}
return treeType;
}
public static int getCreatedTreeTypesCount() {
return treeTypes.size();
}
}
// 외재적 상태 (Extrinsic State) - 각 나무마다 다른 부분
class Tree {
private int x, y, size; // 위치와 크기 (각 나무마다 다름)
private TreeType treeType; // 공유되는 부분
public Tree(int x, int y, int size, TreeType treeType) {
this.x = x;
this.y = y;
this.size = size;
this.treeType = treeType;
}
public void render(Canvas canvas) {
treeType.render(canvas, x, y, size); // 공유 객체에 개별 데이터 전달
}
}
// 숲 (많은 나무들을 관리)
class Forest {
private List<Tree> trees = new ArrayList<>();
public void plantTree(int x, int y, int size, String name, String color, String sprite) {
TreeType treeType = TreeTypeFactory.getTreeType(name, color, sprite);
Tree tree = new Tree(x, y, size, treeType);
trees.add(tree);
}
public void render(Canvas canvas) {
trees.forEach(tree -> tree.render(canvas));
}
}
// 사용
Forest forest = new Forest();
Canvas canvas = new Canvas();
// 10,000그루의 나무 심기
for (int i = 0; i < 10000; i++) {
forest.plantTree(
random(0, 1000), random(0, 1000), random(1, 10),
"Oak", "Green", "oak_sprite.png" // 대부분 같은 종류
);
}
// 실제로는 TreeType 객체가 몇 개만 생성됨
System.out.println("생성된 TreeType 개수: " + TreeTypeFactory.getCreatedTreeTypesCount());
// "생성된 TreeType 개수: 1" (10,000그루지만 TreeType은 1개만!)
forest.render(canvas);
메모리 절약 효과:
- Flyweight 사용 안 함: 10,000개 Tree 객체 × 전체 데이터 = 매우 큰 메모리
- Flyweight 사용: 1개 TreeType 객체 + 10,000개의 위치 데이터 = 훨씬 적은 메모리
언제 사용하나?
- 많은 수의 비슷한 객체를 생성해야 할 때
- 객체 생성 비용이 클 때
- 게임의 파티클 시스템, 텍스트 에디터의 문자 렌더링
7. Proxy - "다른 객체에 대한 대리자"
핵심 개념: 다른 객체에 대한 인터페이스 역할을 하는 객체를 제공하는 패턴
실생활 비유:
- 변호사 (본인 대신 법정에서 변론)
- 부동산 중개사 (집주인 대신 집을 보여줌)
- 신용카드 (실제 돈 대신 결제 처리)
java
interface Image {
void display();
}
// 실제 객체 (비용이 많이 드는 작업)
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk(); // 무거운 작업 (실제로는 파일 로딩)
}
private void loadImageFromDisk() {
System.out.println("디스크에서 이미지 로딩: " + filename);
// 실제로는 시간이 오래 걸리는 작업
try { Thread.sleep(2000); } catch (InterruptedException e) {}
}
public void display() {
System.out.println("이미지 표시: " + filename);
}
}
// Proxy 객체
class ProxyImage implements Image {
private String filename;
private RealImage realImage; // 실제 객체 (필요할 때까지 생성하지 않음)
public ProxyImage(String filename) {
this.filename = filename;
// 실제 이미지는 아직 로딩하지 않음!
}
public void display() {
if (realImage == null) {
System.out.println("프록시: 실제 이미지를 처음 요청받았으므로 로딩 시작");
realImage = new RealImage(filename); // 지연 로딩 (Lazy Loading)
}
realImage.display();
}
}
// 사용
System.out.println("=== 프록시 사용 ===");
Image image1 = new ProxyImage("photo1.jpg");
Image image2 = new ProxyImage("photo2.jpg");
System.out.println("이미지 객체 생성 완료 (아직 실제 로딩은 안 됨)");
System.out.println("\n첫 번째 이미지 표시:");
image1.display(); // 이때 실제 로딩 발생
System.out.println("\n첫 번째 이미지 다시 표시:");
image1.display(); // 이미 로딩되어 있으므로 바로 표시
/*
출력:
=== 프록시 사용 ===
이미지 객체 생성 완료 (아직 실제 로딩은 안 됨)
첫 번째 이미지 표시:
프록시: 실제 이미지를 처음 요청받았으므로 로딩 시작
디스크에서 이미지 로딩: photo1.jpg
이미지 표시: photo1.jpg
첫 번째 이미지 다시 표시:
이미지 표시: photo1.jpg
*/
Proxy의 다양한 종류:
- 가상 프록시 (Virtual Proxy) - 위 예제처럼 지연 로딩
- 보안 프록시 (Protection Proxy) - 접근 권한 제어
- 캐싱 프록시 - 결과를 캐시해서 성능 향상
- 원격 프록시 (Remote Proxy) - 원격 객체를 로컬에서 사용하는 것처럼
java
// 보안 프록시 예제
class ProtectionProxyImage implements Image {
private RealImage realImage;
private String filename;
private String userRole;
public ProtectionProxyImage(String filename, String userRole) {
this.filename = filename;
this.userRole = userRole;
}
public void display() {
if (hasAccess()) {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
} else {
System.out.println("접근 권한이 없습니다!");
}
}
private boolean hasAccess() {
return "ADMIN".equals(userRole) || "USER".equals(userRole);
}
}
언제 사용하나?
- 무거운 객체의 생성을 지연하고 싶을 때 (가상 프록시)
- 객체 접근을 제어하고 싶을 때 (보안 프록시)
- 네트워크를 통한 객체 접근 시 (원격 프록시)
- 결과를 캐시하고 싶을 때 (캐싱 프록시)
구조 패턴들의 공통점과 차이점
공통점: 모두 객체들 간의 관계를 다루며, 복잡성을 줄이고 유연성을 높임
차이점:
- Adapter: 인터페이스 변환에 집중
- Bridge: 추상화와 구현의 분리에 집중
- Composite: 트리 구조 처리에 집중
- Decorator: 기능 확장에 집중
- Facade: 복잡성 숨기기에 집중
- Flyweight: 메모리 최적화에 집중
- Proxy: 접근 제어나 지연 처리에 집중
행동 패턴 (Behavioral Patterns) - 11개
Chain of Responsibility - "책임의 사슬로 요청 처리"
java
abstract class Logger {
protected int level;
protected Logger nextLogger;
void setNext(Logger nextLogger) { this.nextLogger = nextLogger; }
void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
abstract void write(String message);
}
// ConsoleLogger -> FileLogger -> ErrorLogger 순서로 체인 구성
실제 사용 예: 로깅 시스템, 이벤트 처리, 권한 검사, 웹 미들웨어
Command - "명령을 객체로 만들어 저장/실행"
java
interface Command {
void execute();
void undo();
}
class LightOnCommand implements Command {
private Light light;
void execute() { light.on(); }
void undo() { light.off(); }
}
class RemoteControl {
private Command[] commands = new Command[7];
private Command lastCommand;
void pressButton(int slot) {
commands[slot].execute();
lastCommand = commands[slot];
}
void pressUndo() { lastCommand.undo(); }
}
실제 사용 예: GUI 버튼 액션, Undo/Redo 기능, 매크로 기능, 작업 큐
Interpreter - "언어의 문법을 해석"
java
interface Expression {
boolean interpret(String context);
}
class TerminalExpression implements Expression {
private String data;
boolean interpret(String context) {
return context.contains(data);
}
}
class OrExpression implements Expression {
private Expression expr1, expr2;
boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
// "Java OR Python" 같은 표현식 해석
실제 사용 예: 정규 표현식, SQL 파서, 수식 계산기, 설정 파일 파서
Iterator - "컬렉션의 요소들을 순차적으로 접근"
java
interface Iterator {
boolean hasNext();
Object next();
}
class BookShelf implements Iterable {
private Book[] books;
public Iterator iterator() {
return new BookShelfIterator(books);
}
}
class BookShelfIterator implements Iterator {
private Book[] books;
private int position = 0;
boolean hasNext() { return position < books.length; }
Object next() { return books[position++]; }
}
// 사용: for-each 루프와 같은 방식으로 순회
for (Book book : bookShelf) {
System.out.println(book.getTitle());
}
실제 사용 예: Java의 Collection 프레임워크, 데이터베이스 결과 집합 순회
Mediator - "객체들 간의 복잡한 상호작용을 중재"
java
interface Mediator {
void notify(Component sender, String event);
}
class DialogMediator implements Mediator {
private Button button;
private Textbox textbox;
private Checkbox checkbox;
void notify(Component sender, String event) {
if (sender == checkbox && event.equals("check")) {
textbox.setEnabled(false);
button.setEnabled(true);
}
// 컴포넌트들 간의 복잡한 상호작용을 중재
}
}
실제 사용 예: GUI 다이얼로그의 컴포넌트 상호작용, 채팅방, 항공 교통 관제
Memento - "객체의 상태를 저장하고 복원"
java
class Originator { // 상태를 가진 객체
private String state;
Memento saveStateToMemento() {
return new Memento(state);
}
void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
class Memento { // 상태를 저장하는 객체
private final String state;
Memento(String state) { this.state = state; }
String getState() { return state; }
}
class CareTaker { // 메멘토를 관리하는 객체
private List<Memento> mementoList = new ArrayList<>();
void add(Memento memento) { mementoList.add(memento); }
Memento get(int index) { return mementoList.get(index); }
}
실제 사용 예: 텍스트 에디터의 Undo 기능, 게임 저장/로드, 체크포인트 시스템
Observer - "구독-알림 시스템"
java
interface Observer {
void update(String message);
}
class Subject {
private List<Observer> observers = new ArrayList<>();
void attach(Observer observer) { observers.add(observer); }
void detach(Observer observer) { observers.remove(observer); }
void notifyObservers(String message) {
observers.forEach(observer -> observer.update(message));
}
}
class NewsAgency extends Subject {
void setNews(String news) {
notifyObservers(news); // 모든 구독자에게 알림
}
}
실제 사용 예: 이벤트 시스템, 모델-뷰 아키텍처, 주식 가격 변동 알림, 소셜 미디어 알림
State - "상태에 따라 행동이 바뀌는 객체"
java
interface State {
void doAction(Context context);
}
class StartState implements State {
void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
}
class Context {
private State state;
void setState(State state) { this.state = state; }
void doAction() { state.doAction(this); }
}
// 자판기 예: 동전없음 -> 동전있음 -> 상품선택 -> 상품배출 상태 변화
실제 사용 예: 게임 캐릭터 상태(걷기/뛰기/공격), TCP 연결 상태, 자판기 상태
Strategy - "알고리즘을 갈아끼우기"
java
interface PaymentStrategy {
void pay(int amount);
}
class CreditCardStrategy implements PaymentStrategy {
void pay(int amount) { System.out.println("Paid " + amount + " using Credit Card"); }
}
class PayPalStrategy implements PaymentStrategy {
void pay(int amount) { System.out.println("Paid " + amount + " using PayPal"); }
}
class ShoppingCart {
private PaymentStrategy paymentStrategy;
void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
void checkout(int amount) {
paymentStrategy.pay(amount); // 선택된 전략으로 결제
}
}
실제 사용 예: 정렬 알고리즘 선택, 결제 방식 선택, 압축 알고리즘 선택, 로그인 방식 선택
Template Method - "큰 틀은 정해두고 세부사항만 바꾸기"
java
abstract class DataMiner {
// 템플릿 메서드: 알고리즘의 골격을 정의
final void mine(String path) {
openFile(path);
extractData();
parseData();
analyzeData();
sendReport();
closeFile();
}
abstract void extractData(); // 서브클래스에서 구현
abstract void parseData(); // 서브클래스에서 구현
// 공통 구현
void openFile(String path) { /* 파일 열기 */ }
void closeFile() { /* 파일 닫기 */ }
}
class PDFDataMiner extends DataMiner {
void extractData() { /* PDF 데이터 추출 */ }
void parseData() { /* PDF 데이터 파싱 */ }
}
실제 사용 예: 데이터 처리 파이프라인, 테스트 프레임워크, 웹 프레임워크의 요청 처리
Visitor - "객체 구조를 변경하지 않고 새로운 연산 추가"
java
interface Visitor {
void visit(Circle circle);
void visit(Rectangle rectangle);
}
class AreaCalculator implements Visitor {
void visit(Circle circle) {
area = Math.PI * circle.radius * circle.radius;
}
void visit(Rectangle rectangle) {
area = rectangle.width * rectangle.height;
}
}
abstract class Shape {
abstract void accept(Visitor visitor);
}
class Circle extends Shape {
void accept(Visitor visitor) {
visitor.visit(this); // 자신을 방문자에게 전달
}
}
// 새로운 기능(둘레 계산)을 추가할 때 기존 도형 클래스 수정 없이 새 Visitor만 추가
실제 사용 예: 컴파일러의 AST 처리, 파일 시스템 탐색, 문서 구조 분석
실무에서의 활용도
매우 자주 사용: Singleton, Factory Method, Observer, Strategy, Decorator, Adapter 프레임워크에서 흔함: Template Method, Command, Iterator, Proxy 특수한 상황에서 유용: Flyweight, Interpreter, Visitor, Memento 복잡한 시스템에서: Abstract Factory, Builder, Mediator, Chain of Responsibility