[Day18] C++ Polymorphism with Movie Management System

베리투스·2025년 8월 28일

TIL: Today I Learned

목록 보기
26/93

C++의 객체 지향 특성 중 하나인 다형성(Polymorphism)을 활용하여 영화 관리 시스템을 구현하는 방법을 학습한다. 🎬 MovieManager 클래스가 영화 데이터를 관리하고, MovieProcessor 인터페이스를 통해 다양한 방식으로 영화 목록을 처리한다. 이를 통해 코드의 유연성과 확장성을 높일 수 있다. ✨


📌 목표

  • C++에서 추상 클래스(인터페이스)와 상속을 활용한 다형성 이해
  • vector, map, sort 등 표준 라이브러리 컨테이너와 알고리즘 사용법 익히기
  • 영화 관리 시스템을 직접 구현하며 객체 지향 설계 원칙 적용하기

💻 코드

main.cpp

#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 컨테이너를 수정할 수 있다는 점을 간과했다. 💡 (정렬 시 컨테이너 내용이 변경됨)
  • processMoviesMovieManager와 독립된 별개의 클래스인 줄 알았다. 알고 보니 MovieManager멤버 함수였고, 외부에서 전달받은 MovieProcessor 파생 객체의 process 함수를 동적으로 바인딩하여 호출하는 중계 지점 역할을 한다는 것을 깨달았다. 🔧

✅ 핵심 요약

개념설명비고
다형성 (Polymorphism)하나의 인터페이스 또는 기본 클래스 참조를 통해 다양한 파생 클래스의 객체를 처리하는 객체 지향 특성. 런타임에 적절한 함수가 호출된다.유연하고 확장 가능한 코드 작성에 필수
추상 클래스 (Abstract Class)하나 이상의 순수 가상 함수를 포함하는 클래스. 객체를 직접 생성할 수 없으며, 파생 클래스에서 순수 가상 함수를 반드시 구현해야 한다.인터페이스 역할을 하며, "무엇을 할지" 규정
순수 가상 함수 (Pure Virtual Function)선언만 있고 구현이 없는 가상 함수 (= 0). 파생 클래스에서 반드시 오버라이드해야 한다.추상 클래스의 핵심 구성 요소
vector동적으로 크기가 조절되는 배열(시퀀스 컨테이너). 요소 추가/삭제가 유연하다.효율적인 데이터 저장 및 관리
mapkey-value 쌍을 저장하는 연관 컨테이너. key를 기준으로 자동 정렬되며, 빠른 검색이 가능하다 (로그 시간 복잡도).데이터 검색 및 매핑에 유용
sortalgorithm 헤더에 정의된 범용 정렬 알고리즘. 반복자 범위와 비교 함수를 인자로 받아 정렬을 수행한다.다양한 조건으로 데이터 정렬 가능
explicit 키워드단일 인자를 받는 생성자의 암묵적 형 변환을 방지한다.의도치 않은 객체 생성을 막아 코드 안정성 향상
override 키워드파생 클래스에서 기본 클래스의 가상 함수를 재정의함을 명시한다.컴파일러에게 오버라이드 의도를 알려 오류 방지

profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글