1. 오늘 배운 개념 (What I Learned)
오늘은 디자인 패턴 중 가장 널리 쓰이는 옵저버 패턴(Observer Pattern)을 C++로 직접 구현해 보았다.
옵저버 패턴이란?
- 주체(Subject)의 상태 변화를 관찰자(Observer)들에게 자동으로 통지하는 디자인 패턴이다.
- 일대다(1:N) 의존 관계를 가지며, 클래스 간의 결합도를 낮춰(Loose Coupling) 유지보수와 확장성을 높이는 데 기여한다.
- 이번 실습에서는 배송 회사(DeliveryService = Subject)의 배송 상태가 바뀔 때마다 등록된 고객들(Customer = Observer)에게 자동으로 알림이 가도록 설계했다.
2. 핵심 구현 포인트 & 코드 리뷰
💡 주요 개선 사항
- 다형성(Polymorphism) 활용: Customer 부모 클래스를 순수 가상 함수(update())를 가진 인터페이스로 만들고, 이를 상속받는 일반/VIP/비즈니스 고객 클래스들을 구현하여 각자 고유한 알림 메시지를 출력하도록 했다.
- 동적 삭제 처리: removeCustomer 구현 시, 단순히 맨 뒤 요소를 지우는 pop_back() 대신 반복자(Iterator)를 활용해 원하는 특정 고객 객체만 안전하게 리스트에서 제거하도록 수정했다.
- 가상 소멸자(Virtual Destructor): 다형성을 안전하게 활용하기 위해 부모 클래스(Customer)에 virtual ~Customer() {}를 추가하여 메모리 누수(Memory Leak)를 방지했다.
💻 최종 완성 코드
C++
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// [Observer] 고객 인터페이스
class Customer {
public:
virtual void update(const string& status) = 0; // 순수 가상 함수
virtual ~Customer() {} // 가상 소멸자
};
// 일반 고객
class RegularCustomer : public Customer {
private:
string name;
public:
RegularCustomer(const string& name) : name(name) {}
void update(const string& status) override {
cout << "Regular customer " << name << " received update: " << status << endl;
}
};
// VIP 고객
class VIPCustomer : public Customer {
private:
string name;
public:
VIPCustomer(const string& name) : name(name) {}
void update(const string& status) override {
cout << "VIP customer [" << name << "] received VIP update: " << status << endl;
}
};
// 비즈니스 고객
class BusinessCustomer : public Customer {
private:
string name;
public:
BusinessCustomer(const string& name) : name(name) {}
void update(const string& status) override {
cout << "Business customer [" << name << "] received Business update: " << status << endl;
}
};
// [Subject] 배송 회사 클래스
class DeliveryService {
private:
vector<Customer*> customers; // 관찰자 명단
string currentStatus; // 현재 배송 상태
public:
void addCustomer(Customer* customer) {
customers.push_back(customer);
}
void removeCustomer(Customer* customer) {
for (auto it = customers.begin(); it != customers.end(); ++it) {
if (*it == customer) {
customers.erase(it);
break;
}
}
}
// 등록된 모든 고객(Observer)에게 전파
void notifyCustomers() {
for (Customer* customer : customers) {
customer->update(currentStatus);
}
}
// 상태 변경 및 알림 트리거
void updateStatus(string text) {
currentStatus = text;
notifyCustomers();
}
};
int main() {
DeliveryService service;
Customer* customer1 = new RegularCustomer("Alice");
Customer* customer2 = new VIPCustomer("Bob");
Customer* customer3 = new BusinessCustomer("CompanyX");
service.addCustomer(customer1);
service.addCustomer(customer2);
service.addCustomer(customer3);
cout << "Updating status: 배송 준비 중" << endl;
service.updateStatus("배송 준비 중");
cout << "\nUpdating status: 배송 완료" << endl;
service.updateStatus("배송 완료");
// 메모리 해제
delete customer1;
delete customer2;
delete customer3;
return 0;
}
3. 실행 결과 (Result)

4. 오늘의 회고 (Retrospective)
- 느낀 점: DeliveryService 클래스는 대소문자나 출력 형식이 다른 개별 고객 클래스들의 내부 사정을 전혀 몰라도, 오직 부모 타입인 Customer* 인터페이스만을 바라보고 통지할 수 있다는 점이 놀라웠다. 이래서 객체지향 프로그래밍에서 "인터페이스에 의존하라"고 하는지 몸소 깨달을 수 있었다.
- 주의할 점: C++에서 상속과 포인터를 다룰 때는 반드시 부모 클래스의 가상 소멸자(virtual)를 잊지 말아야겠다. 또한 vector에서 특정 요소를 지울 때는 인덱스나 pop_back의 오용을 조심하고 iterator 기반으로 정확히 접근해야 안전하다는 것을 배웠다.
'TIL' 카테고리의 다른 글
| [UE5] 언리얼 C++기초 정리 (0) | 2026.06.05 |
|---|---|
| [UE5] 언리얼 C++ 첫걸음: Visual Studio에서 Rider로 IDE 전환 및 트러블슈팅 총정리 (0) | 2026.06.04 |
| [C++] 배치고사 틀린문제 복습 (0) | 2026.06.01 |
| [C++] Text RPG 프로젝트 개인 회고록 (1) | 2026.05.29 |
| [C++] 문자열 대소문자 변환 구현 및 탐색적 학습 방법론 (0) | 2026.05.28 |