백엔드 개발자

[디자인 패턴] 행동 패턴 본문

카테고리 없음

[디자인 패턴] 행동 패턴

임잠탱 2025. 2. 9. 21:12

디자인 패턴은 효율적이고 유지보수하기 쉬운 코드 구조를 만들기 위한 설계 원칙.
그중에서도 행동(Behavioral) 패턴은 객체 간의 상호작용 및 책임 분배를 최적화하는 패턴들로, 객체 간 결합도를 줄이고 유연성을 높이는 데 중요한 역할을 한다.


1. 옵저버 패턴 (Observer Pattern)

→ 주체(Subject)의 상태 변화가 있을 때, 이를 감지하고 자동으로 알림을 받는 패턴

✅ 개념

  • 한 객체(Subject)의 상태가 변하면 등록된 옵저버(Observer)들에게 자동으로 알림을 보냄
  • 이벤트 기반 시스템에서 많이 활용됨
  • MVC(Model-View-Controller) 패턴에서 View가 Model의 변경 사항을 감지할 때 사용됨

✅ 구현 방법 (Java 코드)

import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update(String message);
}

class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " received message: " + message);
    }
}

class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

// 실행 예제
public class ObserverPatternExample {
    public static void main(String[] args) {
        Subject subject = new Subject();
        Observer observer1 = new ConcreteObserver("Observer 1");
        Observer observer2 = new ConcreteObserver("Observer 2");

        subject.addObserver(observer1);
        subject.addObserver(observer2);

        subject.notifyObservers("Hello, Observers!");
    }
}

✅ 특징

  • Subject는 Observer 목록을 관리하고, 상태 변경 시 자동으로 알림을 보냄
  • 느슨한 결합(Loose Coupling)을 제공하여 유지보수가 용이함

✅ 실제 사용 예시

  • 이벤트 리스너 (GUI 프로그램에서 버튼 클릭 이벤트 처리)
  • 메시징 시스템 (Kafka, RabbitMQ)
  • Stock Price Tracker (주식 시장에서 가격 변경 감지)

2. 전략 패턴 (Strategy Pattern)

→ 실행 중에 알고리즘을 바꿀 수 있도록 인터페이스를 정의하는 패턴

✅ 개념

  • 여러 개의 알고리즘을 동적으로 변경할 수 있도록 인터페이스를 분리
  • if-else 분기문을 없애고 코드 유지보수를 용이하게 함

✅ 구현 방법 (Java 코드)

interface PaymentStrategy {
    void pay(int amount);
}

class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card.");
    }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal.");
    }
}

class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// 실행 예제
public class StrategyPatternExample {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        cart.setPaymentStrategy(new CreditCardPayment());
        cart.checkout(100);

        cart.setPaymentStrategy(new PayPalPayment());
        cart.checkout(200);
    }
}

✅ 특징

  • 코드 재사용성이 높아지고, 새로운 알고리즘 추가가 용이
  • 클라이언트 코드가 알고리즘의 구현을 몰라도 됨

✅ 실제 사용 예시

  • 결제 시스템 (신용카드, PayPal, 네이버페이 등)
  • 데이터 압축 알고리즘 선택 (ZIP, RAR, GZIP)
  • AI 게임 캐릭터 행동 변경

3. 상태 패턴 (State Pattern)

→ 객체의 상태에 따라 행동을 다르게 수행하는 패턴

✅ 개념

  • 객체가 다양한 상태를 가질 때, 상태에 따라 동작을 변경하는 패턴
  • if-else 없이 상태별 클래스를 생성하여 유지보수성을 높임

✅ 구현 방법 (Java 코드)

interface State {
    void handle();
}

class StartState implements State {
    public void handle() {
        System.out.println("Game is in Start State.");
    }
}

class PlayState implements State {
    public void handle() {
        System.out.println("Game is in Play State.");
    }
}

class Context {
    private State state;

    public void setState(State state) {
        this.state = state;
    }

    public void execute() {
        state.handle();
    }
}

// 실행 예제
public class StatePatternExample {
    public static void main(String[] args) {
        Context game = new Context();

        game.setState(new StartState());
        game.execute();

        game.setState(new PlayState());
        game.execute();
    }
}

✅ 특징

  • 객체의 상태 전이에 따른 행동을 캡슐화
  • if-else 문을 줄이고 코드의 확장성을 높임

✅ 실제 사용 예시

  • 게임에서 캐릭터의 상태 관리 (Idle, Attack, Dead)
  • 문서 편집기에서 파일 상태 (읽기 전용, 편집 가능)
  • 스레드의 실행 상태 (Runnable, Blocked, Terminated)

4. 반복자 패턴 (Iterator Pattern)

✅ 개념

반복자(Iterator) 패턴은 컬렉션(배열, 리스트 등) 요소들을 순차적으로 접근할 수 있도록 하는 디자인 패턴이다.
내부 구현을 드러내지 않고도 순회(Iteration)를 할 수 있는 방법을 제공한다.

💡 왜 반복자 패턴을 사용할까?

  1. 컬렉션의 내부 구조를 알 필요 없이 순회 가능
  2. 하나의 반복자로 다양한 컬렉션을 다룰 수 있음 (유연성)
  3. 일관된 인터페이스로 여러 자료구조를 순회할 수 있음
  4. 객체 간 결합도를 낮추고(Loosely Coupled), 유지보수를 쉽게 만듦

✅ 반복자 패턴 구조

  • Iterator 인터페이스: 컬렉션의 요소들을 순회하는 메서드를 정의
  • ConcreteIterator (구체적인 반복자): 실제 순회 기능을 구현하는 클래스
  • Aggregate (컬렉션 인터페이스): 컬렉션을 관리하고, 반복자를 생성하는 메서드를 제공
  • ConcreteAggregate (구체적인 컬렉션): 컬렉션 데이터를 저장하고, 반복자를 반환하는 클래스

✅ 반복자 패턴 구현 (Java)

📌 1. Iterator 인터페이스 정의

interface Iterator<T> {
    boolean hasNext();  // 다음 요소가 있는지 확인
    T next();           // 다음 요소 반환
}

📌 2. ConcreteIterator 구현 (구체적인 반복자)

class BookIterator implements Iterator<Book> {
    private List<Book> books;
    private int index = 0;

    public BookIterator(List<Book> books) {
        this.books = books;
    }

    @Override
    public boolean hasNext() {
        return index < books.size();
    }

    @Override
    public Book next() {
        return books.get(index++);
    }
}

📌 3. Aggregate 인터페이스 정의 (컬렉션 인터페이스)

interface IterableCollection<T> {
    Iterator<T> createIterator(); // 반복자 생성 메서드
}

📌 4. ConcreteAggregate 구현 (구체적인 컬렉션)

import java.util.ArrayList;
import java.util.List;

class BookCollection implements IterableCollection<Book> {
    private List<Book> books = new ArrayList<>();

    public void addBook(Book book) {
        books.add(book);
    }

    @Override
    public Iterator<Book> createIterator() {
        return new BookIterator(books);
    }
}

📌 5. Book 객체 정의

class Book {
    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }
}

📌 6. 실행 코드 (Iterator 패턴 적용)

public class IteratorPatternExample {
    public static void main(String[] args) {
        BookCollection bookCollection = new BookCollection();
        bookCollection.addBook(new Book("Design Patterns"));
        bookCollection.addBook(new Book("Effective Java"));
        bookCollection.addBook(new Book("Clean Code"));

        Iterator<Book> iterator = bookCollection.createIterator();

        while (iterator.hasNext()) {
            Book book = iterator.next();
            System.out.println("Book Title: " + book.getTitle());
        }
    }
}

✅ 실행 결과

Book Title: Design Patterns
Book Title: Effective Java
Book Title: Clean Code

✅ 특징

  • 컬렉션의 내부 구조를 감추고 반복 가능 → 캡슐화(Encapsulation) 향상
  • 여러 종류의 컬렉션에 대해 같은 방식으로 순회 가능 → 유연성 증가
  • 일관된 인터페이스 제공 → 코드의 유지보수성 향상

✅ 실제 사용 예시

1️⃣ Java 컬렉션 프레임워크 (Java Collection Framework)

-   Iterator<E> 인터페이스 사용:

List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

위의 방식은 반복자 패턴을 활용한 컬렉션 순회 방식이다.


2️⃣ 데이터베이스 결과셋 처리 (JDBC)

-   JDBC의 ResultSet 객체는 반복자 패턴을 사용:
ResultSet rs = statement.executeQuery("SELECT * FROM users");
while (rs.next()) {
    System.out.println("User: " + rs.getString("name"));
}

3️⃣ 게임 개발 - NPC 순회

  • 게임의 AI에서 NPC 리스트를 순회하는 경우
  • ex) NPC를 탐색하고 특정 행동을 수행할 때 반복자 패턴 사용

5. 템플릿 메서드 패턴 (Template Method Pattern)

✅ 개념

템플릿 메서드 패턴(Template Method Pattern)은 상위 클래스에서 알고리즘의 기본 구조(템플릿)를 정의하고, 하위 클래스에서 세부적인 구현을 결정하는 디자인 패턴이다.

즉, 공통된 알고리즘의 뼈대를 제공하면서도 일부 단계는 서브클래스에서 구현하도록 강제하는 패턴이다.


✅ 왜 템플릿 메서드 패턴을 사용할까?

  1. 알고리즘의 흐름을 통제하면서, 일부 단계만 변경할 수 있도록 유연성을 제공
  2. 코드 중복을 줄이고, 유지보수를 쉽게 만듦
  3. 객체지향 설계 원칙 중 "Hollywood Principle"을 따름
    "Don't call us, we'll call you" (하위 클래스에서 직접 호출하는 것이 아니라, 상위 클래스가 호출)

✅ 템플릿 메서드 패턴 구조

역할설명

AbstractClass (추상 클래스) 알고리즘의 기본 구조(템플릿)를 정의하고, 일부 단계를 추상 메서드로 선언
ConcreteClass (구체적인 하위 클래스) 상위 클래스에서 정의한 알고리즘의 특정 단계를 구현

 


✅ 템플릿 메서드 패턴 구현 (Java 예제)

📌 1. AbstractClass (추상 클래스) 정의

abstract class Game {
    // 템플릿 메서드 (알고리즘의 틀을 정의)
    public final void play() {
        initialize();
        startPlay();
        endPlay();
    }

    // 공통 로직 (변경되지 않는 부분)
    private void initialize() {
        System.out.println("게임을 초기화합니다.");
    }

    // 하위 클래스에서 구현해야 하는 메서드
    protected abstract void startPlay();
    protected abstract void endPlay();
}

✅ play() 메서드가 템플릿 메서드이며, initialize()는 공통 로직, startPlay()와 endPlay()는 하위 클래스가 구현해야 하는 메서드.


📌 2. ConcreteClass (구체적인 하위 클래스) 구현

class Football extends Game {
    @Override
    protected void startPlay() {
        System.out.println("축구 경기를 시작합니다!");
    }

    @Override
    protected void endPlay() {
        System.out.println("축구 경기가 종료되었습니다.");
    }
}

class Basketball extends Game {
    @Override
    protected void startPlay() {
        System.out.println("농구 경기를 시작합니다!");
    }

    @Override
    protected void endPlay() {
        System.out.println("농구 경기가 종료되었습니다.");
    }
}

✅ Football과 Basketball 클래스는 Game 클래스를 상속받아 startPlay()와 endPlay()를 구현함.


📌 3. 실행 코드

public class TemplateMethodExample {
    public static void main(String[] args) {
        Game football = new Football();
        football.play();

        System.out.println();

        Game basketball = new Basketball();
        basketball.play();
    }
}

✅ 실행 결과

게임을 초기화합니다.
축구 경기를 시작합니다!
축구 경기가 종료되었습니다.

게임을 초기화합니다.
농구 경기를 시작합니다!
농구 경기가 종료되었습니다.

✅ play() 메서드는 initialize() → startPlay() → endPlay() 순서로 실행되며,
각 게임마다 startPlay()와 endPlay()가 다르게 동작함.


✅ 특징

코드 재사용성 증가 → initialize() 같은 공통 로직을 상속받아 여러 클래스에서 사용 가능
알고리즘의 흐름을 유지하면서 세부 구현은 하위 클래스에서 결정
클라이언트가 알고리즘의 세부사항을 몰라도 됨


✅ 실제 사용 예시

1️⃣ Java의 java.io.InputStream (템플릿 메서드 패턴 사용)

  • read() 메서드가 템플릿 메서드이며, 하위 클래스에서 read(byte[] b, int off, int len) 등을 구현
InputStream inputStream = new FileInputStream("file.txt");
int data;
while ((data = inputStream.read()) != -1) {
    System.out.print((char) data);
}

2️⃣ Spring Framework에서 템플릿 메서드 패턴 적용 예시

  • JdbcTemplate을 사용하면, DB 연결, SQL 실행, 리소스 해제 등의 공통 과정이 캡슐화됨
  • 개발자는 SQL 실행 부분만 구현하면 됨.
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List<User> users = jdbcTemplate.query("SELECT * FROM users", 
    (rs, rowNum) -> new User(rs.getInt("id"), rs.getString("name"))
);

✅ query() 메서드는 템플릿 메서드 역할을 하고,
✅ 개발자는 람다식으로 RowMapper를 구현하여 필요한 로직만 제공하면 됨.


3️⃣ 게임 개발 - AI 행동 패턴

  • AI 적(enemy)이 같은 알고리즘을 따르지만, 개별 행동(공격 방식 등)이 다를 때 사용
abstract class EnemyAI {
    public final void attack() {
        move();
        specialMove();
        retreat();
    }

    protected abstract void move();
    protected abstract void specialMove();

    private void retreat() {
        System.out.println("적이 후퇴합니다.");
    }
}

class Goblin extends EnemyAI {
    protected void move() {
        System.out.println("고블린이 빠르게 이동합니다.");
    }

    protected void specialMove() {
        System.out.println("고블린이 돌을 던집니다!");
    }
}

class Dragon extends EnemyAI {
    protected void move() {
        System.out.println("드래곤이 하늘을 날아다닙니다.");
    }

    protected void specialMove() {
        System.out.println("드래곤이 불을 내뿜습니다!");
    }
}

✅ AI 적들이 같은 attack() 알고리즘을 따르지만, 행동(move()와 specialMove())이 다르게 구현됨.

Comments