⛳️ 인프런 - cs 지식의 정석 강의를 듣고 학습한 내용입니다.
이번에는 행위 패턴에 대해 알아볼 것이다.
행위 패턴은 객체 간의 책임 분배와 통신 방식을 정의하여, 시스템의 행동을 관리하는 패턴들이다.
이터레이터 패턴은 이터레이터(iterator)를 사용하여 컨테이너의 요소들에 접근하는 디자인 패턴이다.
내부 구조를 노출하지 않고 같은 인터페이스로 컨테이너(또는 컬렉션)에 순회하면서도, 구현 세부사항은 숨길 수 있다.
예시로 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
문을 사용해 순회할 수 있다.
결제 전략
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 인터페이스 정의
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가 상태 변화를 '알려주는' 방식으로 동작한다.