[CS] 디자인 패턴 - 행위 패턴

팔랑이·2024년 12월 31일
0

CS

목록 보기
5/19
post-thumbnail

⛳️ 인프런 - cs 지식의 정석 강의를 듣고 학습한 내용입니다.


디자인 패턴

이번에는 행위 패턴에 대해 알아볼 것이다.
행위 패턴은 객체 간의 책임 분배와 통신 방식을 정의하여, 시스템의 행동을 관리하는 패턴들이다.

이터레이터(iterator) 패턴

이터레이터 패턴은 이터레이터(iterator)를 사용하여 컨테이너의 요소들에 접근하는 디자인 패턴이다.
내부 구조를 노출하지 않고 같은 인터페이스로 컨테이너(또는 컬렉션)에 순회하면서도, 구현 세부사항은 숨길 수 있다.

Javascript 예시 코드

예시로 Javascript의 For...of 루프를 들 수 있다.

// Map 객체 생성
const mp = new Map();
mp.set('a', 1);  // 키 'a'에 값 1을 설정
mp.set('b', 2);  // 키 'b'에 값 2를 설정
mp.set('cccc', 3);  // 키 'cccc'에 값 3을 설정

// Set 객체 생성
const st = new Set();
st.add(1);  // 값 1을 추가
st.add(2);  // 값 2를 추가
st.add(3);  // 값 3을 추가

// 배열 생성
const a = [];
for (let i = 0; i < 10; i++) {
    a.push(i);  // 배열에 0부터 9까지의 숫자 추가
}


// Map을 순회하며 키-값 쌍 출력
for (let pair of mp) {
    console.log(pair);  // Map의 각 [키, 값] 쌍을 출력
}

// Set을 순회하며 값 출력
for (let value of st) {
    console.log(value);  // Set의 각 값을 출력
}

// 배열을 순회하며 값 출력
for (let aa of a) {
    console.log(aa);  // 배열의 각 요소를 출력
}

이처럼 Map, Set, 배열 등 서로 다른 타입의 객체도 동일한 for...of 문을 사용해 순회할 수 있다.


전략 패턴

  • 전략이라고 부르는 '캡슐화된 알고리즘'을 런타임 시 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 디자인 패턴이다.
  • 특정 동작을 상황에 따라 다르게 실행해야 할 때(ex. 결제 방식, 정렬 방식 등) 사용한다.
  • 코드 재사용성을 높이고 조건문을 줄이며, 새로운 알고리즘 추가 시 기존 코드를 수정하지 않아도 된다는 장점이 있다.

Java 전략패턴 예시코드

결제 전략

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

// 결제 전략을 정의하는 인터페이스
interface PaymentStrategy {
    // 결제 메서드. 구현체에서 구체적으로 정의
    public void pay(int amount);
}

// KAKAO 카드로 결제하는 전략: name, cardNumber, cvv, dateOfExpiry가 필요
class KAKAOCardStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;

    public KAKAOCardStrategy(String nm, String ccNum, String cvv, String expiryDate) {
        this.name = nm;
        this.cardNumber = ccNum;
        this.cvv = cvv;
        this.dateOfExpiry = expiryDate;
    }

    // KAKAO 카드를 사용하여 결제하는 구체적 구현
    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using KAKAOCard.");
    }
}

// LUNA 카드로 결제하는 전략: emailId, password가 필요
class LUNACardStrategy implements PaymentStrategy {
    private String emailId;
    private String password;

    public LUNACardStrategy(String email, String pwd) {
        this.emailId = email;
        this.password = pwd;
    }

    // LUNA 카드를 사용하여 결제하는 구체적 구현
    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using LUNACard.");
    }
}

// 쇼핑 아이템을 나타내는 클래스
class Item {
    private String name;
    private int price;

    public Item(String name, int cost) {
        this.name = name;
        this.price = cost;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

// 쇼핑카트 클래스: 컨텍스트
class ShoppingCart {
    List<Item> items;

    public ShoppingCart() {
        this.items = new ArrayList<>();
    }

    public void addItem(Item item) {
        this.items.add(item);
    }

    public void removeItem(Item item) {
        this.items.remove(item);
    }

    public int calculateTotal() {
        int sum = 0;
        for (Item item : items) {
            sum += item.getPrice();
        }
        return sum;
    }

    // 결제 메서드. 결제 전략을 인자로 받아 실행
    public void pay(PaymentStrategy paymentMethod) {
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

// 메인 클래스
public class HelloWorld {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        Item A = new Item("kundolA", 100); // 첫 번째 아이템
        Item B = new Item("kundolB", 300); // 두 번째 아이템
        cart.addItem(A);
        cart.addItem(B);

        // LUNA 카드를 사용한 결제
        cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));

        // KAKAO 카드를 사용한 결제
        cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
    }
}

cart.pay() 안에 캡슐화된 결제전략이 들어가있음을 확인할 수 있다.


옵저버 패턴

객체 간 1:N의 관계를 정의하여 객체의 상태 변화를 관찰(observe)하다가 상태 변화가 있을 시 해당 상태에 의존하는 모든 객체에 자동으로 통지하는 디자인 패턴이다.

  • Subject: 상태를 관리하며 옵저버를 등록/해제/알림 처리한다.
  • Observer: Subject에게 알림을 받으면 이를 감지하고 작업을 수행한다.

Java 예시코드

// Subject 인터페이스 정의
interface Subject {
    public void register(Observer obj); // 옵저버 등록
    public void unregister(Observer obj); // 옵저버 제거
    public void notifyObservers(); // 모든 옵저버에 알림
    public Object getUpdate(Observer obj); // 옵저버가 상태를 요청
}

// Observer 인터페이스 정의
interface Observer {
    public void update(); // 옵저버가 상태 업데이트를 처리
}

// Subject 구현체
class Topic implements Subject {
    private List<Observer> observers; // 등록된 옵저버 목록
    private String message; // Subject 상태

    public Topic() {
        this.observers = new ArrayList<>();
        this.message = ""; // 초기 상태
    }

    @Override
    public void register(Observer obj) {
        if (!observers.contains(obj)) observers.add(obj); // 옵저버 등록
    }

    @Override
    public void unregister(Observer obj) {
        observers.remove(obj); // 옵저버 제거
    }

    @Override
    public void notifyObservers() {
        this.observers.forEach(Observer::update); // 모든 옵저버에 알림
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message; // 현재 상태 반환
    }

    public void postMessage(String msg) {
        System.out.println("Message sended to Topic: " + msg);
        this.message = msg; // 상태 업데이트
        notifyObservers(); // 알림
    }
}

// Observer 구현체
class TopicSubscriber implements Observer {
    private String name; // 옵저버 이름
    private Subject topic; // Subject 참조

    public TopicSubscriber(String name, Subject topic) {
        this.name = name; // 옵저버 이름 설정
        this.topic = topic; // Subject 설정
    }

    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this); // Subject로부터 상태 가져옴
        System.out.println(name + ":: got message >> " + msg); // 알림 표시
    }
}

// main
public class HelloWorld {
    public static void main(String[] args) {
        Topic topic = new Topic(); // Subject 객체 생성
        Observer a = new TopicSubscriber("a", topic); // 옵저버 a 생성
        Observer b = new TopicSubscriber("b", topic); // 옵저버 b 생성
        Observer c = new TopicSubscriber("c", topic); // 옵저버 c 생성

        topic.register(a); // 옵저버 a 등록
        topic.register(b); // 옵저버 b 등록
        topic.register(c); // 옵저버 c 등록

        topic.postMessage("amumu is op champion!!"); // 메시지 게시 (모든 옵저버에 알림)
    }
}

/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/

동작 흐름

살짝 헷갈렸던게, 옵저버 패턴은 Observer가 Subject의 상태변화를 스스로'감지'하는 것이 아니라, Subject가 상태 변화를 '알려주는' 방식으로 동작한다.

  • postMessage(msg)를 호출하면 Topic의 내부 상태(message)가 업데이트된다.
  • notifyObservers() 메서드에서 등록된 모든 옵저버의 update() 메서드를 호출한다.
  • 각 옵저버의 update()메서드는 Subject로부터 새로운 상태(message)를 가져온다.
profile
정체되지 않는 성장

0개의 댓글