C++의 객체 지향 특성 중 하나인 다형성(Polymorphism)을 활용하여 영화 관리 시스템을 구현하는 방법을 학습한다. 🎬 MovieManager 클래스가 영화 데이터를 관리하고, MovieProcessor 인터페이스를 통해 다양한 방식으로 영화 목록을 처리한다. 이를 통해 코드의 유연성과 확장성을 높일 수 있다. ✨
vector, map, sort 등 표준 라이브러리 컨테이너와 알고리즘 사용법 익히기#include <iostream> // 표준 입출력 스트림
#include <vector> // 템플릿 기반 시퀀스 컨테이너: 동적 배열(vector)
#include <map> // 템플릿 기반 연관 컨테이너: key-value(균형 BST) map
#include <algorithm> // sort 등 범용 알고리즘
#include <string> // 문자열 클래스 string
using namespace std; // std:: 접두사 생략
// 영화 정보를 담는 구조체
// 멤버: 제목(title), 평점(rating)
struct Movie {
string title;
double rating;
};
// 영화 목록을 "어떻게 처리할지" 규격만 정의하는 추상 클래스(인터페이스)
// 실제 처리는 파생 클래스에서 구현한다.
class MovieProcessor {
public:
virtual void process(vector<Movie>& movies) = 0; // 순수 가상 함수
virtual ~MovieProcessor() = default;
};
// 영화 데이터를 보유/조회/처리를 연결하는 관리자 클래스
class MovieManager {
private:
vector<Movie> movies; // 영화 목록
map<string, double> movieMap; // 제목→평점 매핑 (빠른 검색용)
public:
// 생성자: 멤버를 초기화하고 map 인덱스를 구축
MovieManager() {
movies = {
{"Inception", 9.0},
{"Interstellar", 8.6},
{"The Dark Knight", 9.1},
{"Memento", 8.4}
};
// movies를 순회해 제목을 key, 평점을 value 로 map에 저장
for (const auto& movie : movies) {
movieMap[movie.title] = movie.rating;
}
}
// 현재 보유 중인 영화 목록 출력
void printMovies() {
cout << "영화 목록:\n";
for (const auto& movie : movies) {
cout << "제목: " << movie.title << ", 평점: " << movie.rating << "\n";
}
}
// 제목으로 영화 한 편을 조회해 출력
void findMovie(const string& title) {
// map::find는 해당 key를 가리키는 반복자(iterator)를 반환
auto it = movieMap.find(title);
if (it != movieMap.end()) {
// it->first: key(제목), it->second: value(평점)
cout << "영화 제목: " << it->first << ", 평점: " << it->second << "\n";
}
else {
cout << "해당 영화는 목록에 없습니다.\n";
}
}
// 다형성: 외부에서 전달받은 처리기(processor)가 정의한 방식으로 movies를 처리
// 인자로 기본 클래스 참조(MovieProcessor&)를 받으므로, 파생 클래스의 process가 동적 바인딩된다.
void processMovies(MovieProcessor& processor) {
processor.process(movies);
}
};
// 정렬 기준: 평점 내림차순이 되도록 비교
// sort에서 true를 반환하면 a가 b보다 앞에 온다.
bool compareMovies(const Movie& a, const Movie& b) {
return a.rating > b.rating;
}
// 정렬 기능을 구현한 파생 클래스
class RatingSorter : public MovieProcessor {
public:
// override: movies를 참조로 받아 실제로 정렬을 수행(컨테이너 수정)
void process(vector<Movie>& movies) override {
sort(movies.begin(), movies.end(), compareMovies); // 평점 내림차순 정렬
cout << "평점 기준 정렬된 영화 목록:\n";
for (const auto& movie : movies) {
cout << "제목: " << movie.title << ", 평점: " << movie.rating << "\n";
}
}
};
// 최소 평점 기준으로 필터링하는 파생 클래스
class RatingFilter : public MovieProcessor {
private:
double minRating; // 외부 접근을 차단한 최소 평점 기준
public:
// explicit: 단일 인자 생성자에서의 암묵적 변환 방지
explicit RatingFilter(double minRating) : minRating(minRating) {}
// override: 기준 이상인 영화만 출력(컨테이너는 수정하지 않음)
void process(vector<Movie>& movies) override {
cout << "평점 " << minRating << " 이상인 영화 목록:\n";
for (const auto& movie : movies) {
if (movie.rating >= minRating) {
cout << "제목: " << movie.title << ", 평점: " << movie.rating << "\n";
}
}
}
};
// 프로그램의 진입점
int main() {
MovieManager manager; // 관리자 객체 생성
cout << "1. 영화 목록 출력\n";
manager.printMovies();
cout << "\n2. 영화 검색 (예: Interstellar)\n";
manager.findMovie("Interstellar");
cout << "\n3. 평점 기준 정렬 및 출력\n";
RatingSorter sorter; // 정렬 전략 객체
manager.processMovies(sorter); // 다형성: sorter.process(movies) 호출
cout << "\n4. 평점 8.5 이상인 영화 필터링 및 출력\n";
RatingFilter filter(8.5); // 필터 전략 객체
manager.processMovies(filter); // 다형성: filter.process(movies) 호출
return 0; // 정상 종료
}
vector를 처음에 "슈퍼 배열"이라고 생각했는데, 정확히는 템플릿 기반 시퀀스 컨테이너라는 점을 놓쳤다. 😅 동적 배열의 개념은 맞지만, 더 포괄적인 의미를 담고 있었다!map::find가 특정 요소의 "주소"를 반환한다고 착각한 적이 있다. 사실은 해당 요소를 가리키는 반복자(iterator)를 반환하는 것이었다. 🔁 반복자를 통해 key와 value에 접근해야 한다.compareMovies 함수를 RatingSorter 클래스의 멤버 함수로 만들어야 한다고 생각했지만, sort 알고리즘은 전역 함수 또는 람다 표현식을 비교자로 받을 수 있다는 것을 알게 되었다. 🤦process(vector<Movie>& movies) 함수의 매개변수를 처음에는 단순히 "읽기 전용"으로만 생각했다. 하지만 &는 비-const 참조이므로, 이 함수 안에서 movies 컨테이너를 수정할 수 있다는 점을 간과했다. 💡 (정렬 시 컨테이너 내용이 변경됨)processMovies를 MovieManager와 독립된 별개의 클래스인 줄 알았다. 알고 보니 MovieManager의 멤버 함수였고, 외부에서 전달받은 MovieProcessor 파생 객체의 process 함수를 동적으로 바인딩하여 호출하는 중계 지점 역할을 한다는 것을 깨달았다. 🔧| 개념 | 설명 | 비고 |
|---|---|---|
| 다형성 (Polymorphism) | 하나의 인터페이스 또는 기본 클래스 참조를 통해 다양한 파생 클래스의 객체를 처리하는 객체 지향 특성. 런타임에 적절한 함수가 호출된다. | 유연하고 확장 가능한 코드 작성에 필수 |
| 추상 클래스 (Abstract Class) | 하나 이상의 순수 가상 함수를 포함하는 클래스. 객체를 직접 생성할 수 없으며, 파생 클래스에서 순수 가상 함수를 반드시 구현해야 한다. | 인터페이스 역할을 하며, "무엇을 할지" 규정 |
| 순수 가상 함수 (Pure Virtual Function) | 선언만 있고 구현이 없는 가상 함수 (= 0). 파생 클래스에서 반드시 오버라이드해야 한다. | 추상 클래스의 핵심 구성 요소 |
vector | 동적으로 크기가 조절되는 배열(시퀀스 컨테이너). 요소 추가/삭제가 유연하다. | 효율적인 데이터 저장 및 관리 |
map | key-value 쌍을 저장하는 연관 컨테이너. key를 기준으로 자동 정렬되며, 빠른 검색이 가능하다 (로그 시간 복잡도). | 데이터 검색 및 매핑에 유용 |
sort | algorithm 헤더에 정의된 범용 정렬 알고리즘. 반복자 범위와 비교 함수를 인자로 받아 정렬을 수행한다. | 다양한 조건으로 데이터 정렬 가능 |
explicit 키워드 | 단일 인자를 받는 생성자의 암묵적 형 변환을 방지한다. | 의도치 않은 객체 생성을 막아 코드 안정성 향상 |
override 키워드 | 파생 클래스에서 기본 클래스의 가상 함수를 재정의함을 명시한다. | 컴파일러에게 오버라이드 의도를 알려 오류 방지 |
