- 소프트웨어를 설계할 때 자주 발생하는 문제들을 해결하기 위해 고안된 해결책이다.
- 디자인 패턴에서 ‘패턴’이라는 단어는 애플리케이션 개발에서 발생하는 문제는 유사한 경우가 많고 해결책도 동일하게 적용할 수 있다는 의미를 내표한다.
- but 디자인 패턴이 모든 문제의 정답은 아니며, 상황에 맞는 최적 패턴을 결정해서 사용
디자인 패턴의 종류
- 대표적 분류 방식인 ‘GoF 디장니 패턴’ == ‘Gang for Four’의 줄임말
- 구체화하고 체계화해서 분류한 4명의 인물 의미
- Gof 디자인 패턴은 생성 패턴, 구조 패턴, 행위 패턴의 총 3가지로 구분됨
생성 패턴 → 객체 생성에 사용되는 패턴, 객체를 수정해도 호출부가 영향 받지 않는다.
구조 패턴 → 객체를 조합해서 더 큰 구조를 만드는 패턴
행위 패턴 → 객체 간의 알고리즘이나 책임 분배에 관한 패턴, 객체 하나로는 수행할 수 없는 작업을 여러 객체를 이용해 작업을 분배. 결합도 최소화를 고려할 필요가 있다.
생성 패턴
1. 싱글턴(Singleton)
하나의 인스턴스만 생성되도록 보장하는 패턴
- 전역적으로 접근 가능
- 스프링의 기본 Bean Scope
특징
- 인스턴스 1개만 유지
- new로 새로 만들 수 없음
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {} // 외부에서 생성 금지
public static Singleton getInstance() {
return instance;
}
}
2. 팩토리 메서드(Factory Method)
객체 생성 로직을 서브 클래스에 위임하여 코드 변경 없이 다른 객체 생성 가능
특징
- 상위 클래스에서 객체 생성 메서드 정의
- 하위 클래스
abstract class Dialog {
public void render() {
Button btn = createButton(); // 팩토리 메서드
btn.onClick();
}
protected abstract Button createButton(); // 추상
}
class WindowsDialog extends Dialog {
protected Button createButton() {
return new WindowsButton();
}
}
3. 추상 팩토리(Abstract Factory)
관련된 객체들을 통일된 인터페이스로 묶어 생성하는 패턴
- 팩토리 묶음
- 서로 호환되는 객체군 생성
특징
- 관련 객체들을 한 번에 생성
- 플랫폼 변경 쉽게 가능 (windows ↔ mac)
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
class MacFactory implements GUIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
}
4. 빌더(Builder)
객체를 단계별로 생성하도록 분리
- 복잡한 객체 생성 시 유용
- 가독성과 재사용성 ↑
특징
- 생성자 파라미터 많을 때 적합
- 선택적 매개변수 가능
public class User {
private String name;
private int age;
public static class Builder {
private String name;
private int age;
public Builder name(String name) { this.name = name; return this; }
public Builder age(int age) { this.age = age; return this; }
public User build() { return new User(name, age); }
}
private User(String name, int age) {
this.name = name;
this.age = age;
}
}
// 사용
User user = new User.Builder().name("Kim").age(20).build();
5. 프로토타입(Prototype)
기존 객체를 복제(clone)하여 새 객체 생성
특징
- 객체 복사로 생성
- DB 로딩 비용이 큰 경우 유용
public class Sheep implements Cloneable {
private String name;
public Sheep(String name) {
this.name = name;
}
public Sheep clone() throws CloneNotSupportedException {
return (Sheep) super.clone();
}
}
// 사용
Sheep original = new Sheep("Dolly");
Sheep copy = original.clone();
구조 패턴
1. 어댑터(Adapter)
기존 인터페이스를 호환되지 않는 다른 인터페이스에 맞게 바꿔줌 → “끼워 넣기” 용도
사용 시기
- 기존 코드 변경 없이 새 클래스를 끼워넣고 싶을 때
// 기존 코드 (클라이언트가 기대하는 인터페이스)
interface MediaPlayer {
void play(String fileName);
}
// 새 클래스 (호환되지 않음)
class VLCPlayer {
void playVLC(String fileName) {
System.out.println("Playing VLC: " + fileName);
}
}
// 어댑터
class MediaAdapter implements MediaPlayer {
private VLCPlayer vlc = new VLCPlayer();
public void play(String fileName) {
vlc.playVLC(fileName); // 호환시켜줌
}
}
2. 브리지(Bridge)
기능 계층과 구현 계층을 분리해서 독립적으로 확장 가능
사용 시기
- 플랫폼과 기능을 독립적으로 바꾸고 싶을 때
interface Device {
void turnOn();
void turnOff();
}
abstract class Remote {
protected Device device;
public Remote(Device device) {
this.device = device;
}
abstract void power();
}
class TV implements Device {
public void turnOn() { System.out.println("TV on"); }
public void turnOff() { System.out.println("TV off"); }
}
class BasicRemote extends Remote {
public BasicRemote(Device device) { super(device); }
void power() { device.turnOn(); }
}
3. 컴포지트(Composite)
부분-전체 구조를 트리 형태로 표현
사용 시기
- 폴더/파일처럼 계층 구조를 동일 인터페이스로 다루고 싶을 때
interface Component {
void show();
}
class FileLeaf implements Component {
private String name;
public FileLeaf(String name) { this.name = name; }
public void show() {
System.out.println("File: " + name);
}
}
class FolderComposite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component c) {
children.add(c);
}
public void show() {
for (Component c : children) c.show();
}
}
4. 데코레이터(Decorater)
기능을 동적으로 추가할 수 있게 하는 패턴(상속 대신 조합)
사용 시기
- 실행 중 기능을 유연하게 붙이고 싶을 때(Spring의 Filter도 유사)
interface Coffee {
String getDescription();
int cost();
}
class BasicCoffee implements Coffee {
public String getDescription() { return "Basic"; }
public int cost() { return 5; }
}
class MilkDecorator implements Coffee {
private Coffee coffee;
public MilkDecorator(Coffee c) { this.coffee = c; }
public String getDescription() { return coffee.getDescription() + ", Milk"; }
public int cost() { return coffee.cost() + 2; }
}
5. 퍼사드(Facade)
복잡한 서브 시스템에 대해 단순한 인터페이스 제공
사용 시기
- 복잡한 시스템을 간단하게 감싸서 제공하고 싶을 때 (ex : JdbcTemplate)
class CPU {
void freeze() {}
void execute() {}
}
class Memory {
void load() {}
}
class ComputerFacade {
private CPU cpu = new CPU();
private Memory memory = new Memory();
public void start() {
cpu.freeze();
memory.load();
cpu.execute();
}
}
6. 플라이웨이트(Flyweight)
공유 객체를 사용해서 메모리 절약 자주 등장하는 작은 객체 캐싱
사용 시기
- 반복되는 값(예 : 글자폰트, 배경 등)을 캐싱해 메모리 절약하고 싶을 때
class Circle {
private String color;
public Circle(String color) { this.color = color; }
}
class CircleFactory {
private static Map<String, Circle> map = new HashMap<>();
public static Circle getCircle(String color) {
return map.computeIfAbsent(color, c -> new Circle(c));
}
}
7. 프록시(Proxy)
실제 객체에 접근 전에 대리 객체를 사용해 제어 접근 제어, 로깅, 지연 로딩 등
사용 시기
- 접근 제어, 리소스 절약, 로깅 등에서 사용됨(Spring AOP도 프록시 기반)
interface Service {
void run();
}
class RealService implements Service {
public void run() {
System.out.println("Running actual logic");
}
}
class ProxyService implements Service {
private RealService real;
public void run() {
if (real == null) real = new RealService(); // 지연 로딩
System.out.println("Proxy: Before");
real.run();
System.out.println("Proxy: After");
}
}
행위 패턴
1. 책임 연쇄 패턴(Chain of Responsibility)
요청을 여러 객체에 순차적으로 전달하면서 처리할 수 있게 함.
사용 시기
- 요청을 여러 객체 중 하나가 처리해야할 때
abstract class Handler {
protected Handler next;
public void setNext(Handler next) { this.next = next; }
public abstract void handle(String request);
}
class AuthHandler extends Handler {
public void handle(String request) {
if (request.equals("auth")) System.out.println("Auth 처리됨");
else if (next != null) next.handle(request);
}
}
2. 커맨드 패턴(Command)
실행될 기능을 요청 객체로 캡슐화해서 요청자와 실행자를 분리
사용 시기
- 요청을 큐에 저장하거나 실행 취소/다시 실행 가능하게 할 때
interface Command {
void execute();
}
class LightOnCommand implements Command {
public void execute() { System.out.println("Light On"); }
}
class RemoteControl {
private Command command;
public void setCommand(Command c) { this.command = c; }
public void pressButton() { command.execute(); }
}
3. 인터프리터 패턴
언어 문법을 클래스 표현하고 해석기로 해석함
사용 시기
- SQL, 정규식 등 언어 해석기 구현에 적합
interface Expression {
boolean interpret(String context);
}
class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data) { this.data = data; }
public boolean interpret(String context) {
return context.contains(data);
}
}
4. 이터레이터 패턴(iterator)
컬렉션 요소들을 순서대로 접근할 수 있게 해줌
사용 시기
- 내부 구현을 노출하지 않고 컬렉션 순회하고 싶을 때
class MyList {
private List<String> items = List.of("a", "b", "c");
public Iterator<String> iterator() {
return items.iterator();
}
}
5. 상태 패턴(State)
객체의 상태에 따라 행동을 변경(if 대신 클래스 분리)
사용 시기
- 상태에 따라 행동이 바뀌는 객체를 설계할 때 (ex : TCP 상태머신)
interface State {
void handle();
}
class OnState implements State {
public void handle() { System.out.println("전원 ON"); }
}
class Context {
private State state;
public void setState(State s) { state = s; }
public void request() { state.handle(); }
}
6. 전략 패턴(Strategy)
알고리즘을 캡슐화해서 동적으로 교체 가능
사용 시기
- 조건문 없이 동작을 교체 가능하게 하고 싶을 때 (ex : Spring의 AuthenticationProvider 전략 변경)
interface Strategy {
int operate(int a, int b);
}
class AddStrategy implements Strategy {
public int operate(int a, int b) { return a + b; }
}
class Calculator {
private Strategy strategy;
public Calculator(Strategy s) { this.strategy = s; }
public int execute(int a, int b) { return strategy.operate(a, b); }
}
7. 템플릿 메서드(Template Method)
알고리즘의 뼈대를 정의하고 일부 로직은 서브클래스에서 구현
사용 시기
- 공통 로직 + 변하는 부분을 분리하고 싶을 때 (ex : AbstractController)
abstract class Game {
public void play() {
start();
playGame();
end();
}
abstract void playGame();
void start() { System.out.println("게임 시작"); }
void end() { System.out.println("게임 종료"); }
}
8. 비지터 패턴(Visitor)
객체 구조를 변경하지 않고 새로운 연산을 추가
사용 시기
- 객체 구조는 그대로 두고 다양한 기능을 추가하고 싶을 때 (ex : 컴팡일러에서 AST 방문)
interface Visitor {
void visit(Book b);
}
class Book {
void accept(Visitor v) { v.visit(this); }
}
class PriceCalculator implements Visitor {
public void visit(Book b) {
System.out.println("책 가격 계산");
}
}
'Spring Boot > 레이어트 아키텍처, 디자인 패턴' 카테고리의 다른 글
[Spring boot] 레이어드 아키텍처 (0) | 2025.07.21 |
---|