728x90

같은 자료형의 여러 객체에 대해 수행할 작업이 많을 때 혹은 작업의 종류가 계속해서 수정되는 경우 여러 종류의 작업을 어떻게 하면 효율적으로 수행할 지를 설계하는 방법에 대해 소개합니다.

 

만약 여러 종류의 계좌를 운영하는 프로그램일 때 계좌 종류별로 클래스를 정의할 수 있습니다. 각 클래스는 입출금, 잔액 조회와 같은 인터페이스를 가지게 됩니다. 고객들은 계좌 객체들을 리스트 형태로 관리하게 되는데 여러 객체들에게 다양한 작업을 해주기도 하고 수행하는 작업도 변경될 수 있습니다. 만약 새로운 작업이 수정되어야 한다면 각 클래스의 멤버함수를 수정해줘야 합니다. 이런 방법에 문제는 작업 종류가 수정될때마다 기존 클래스를 수정해줘야 합니다. 그리고 작업이 비슷해도 모두 각각의 함수를 정의해줘야 합니다. 

 

위와 같은 불편함 없이 새로운 종류의 작업을 추가하기 위해서 클래스 상속을 이용해야 하는데 이 때 작업을 기준으로 잡아야 작업을 변경하기에 적합하기 때문입니다. 그리고 작업 대상과 작업 항목을 연결시키기 위해 작업 항목에 대상을 전달하는 것입니다. 

class Account {
    virtual void Accept(Reporter& rpt) = 0;
    virtual int GetTotalSum() = 0;
};

class PassbookAccount : public Account {
    void Accept(Reporter& rpt) {
        rtp.VisitPassbookAccount(this);
    }
    void GetTotalSum() { return m_nTotalMoeny; }
};

class CheckingAccount : public Account {
    void Accept(Reporter& rpt) {
        rtp.VisitCheckingAccount(this);
    }
    void GetTotalSum() { return m_nTotalMoeny + m_nTotalMoney * 0.1; }
};

class Reporter {
    virtual void VisitPassbookAccount(PassbookAccount* pAccount) = 0;
    virtual void VisitCheckingAccount(CheckingAccount* pAccount) = 0;
};

class TotalSumreporter : public Reporter {
    virtual void VisitPassbookAccount(PassbookAccount* pAccount)
    virtual void VisitCheckingAccount(CheckingAccount* pAccount)
};

class Historyreporter : public Reporter {
    virtual void VisitPassbookAccount(PassbookAccount* pAccount)
    virtual void VisitCheckingAccount(CheckingAccount* pAccount)
};

void DoReporter(list<Account*>& accountList, Reporter& rtp) {
    list<Account*>::iterator iter;
    for ( iter=accountList.begin(); iter!= accountList.end(); iter++) {
        (*iter)->Accept(rtp);
    }
}

int main()
{
    PassbookAccount a, b;
    CheckingAccount c, d;
    
    list<Account*> accountList;
    accountList.push_front(&a);
    accountList.push_front(&b);
    accountList.push_front(&c);
    accountList.push_front(&d);
    
    TotalSumReporter sumRpt;
    HistoryReporter histRpt;
    
    DoReporter(accountList, sumRpt);
    DoReporter(accountList, histRpt);
}

Client는 계좌 객체를 담고 있는 객체 리스트와 동작을 담당하는 객체를 함수로 전달합니다. 그 함수하나로 리스트에 담겨있는 객체에 대해 각기 다른 작업을 수행할 수 있습니다. 그리고 각 작업 대상 객체들은 멤버 함수를 통해 작업 대상을 전달받아 그 객체에 맞는 함수를 호출하게 됩니다. 이렇게 작업 항목과 작업 대상 두가지 자료형에 의해 작업이 달라지는 것을 Double-Dispatch라 합니다.

 

위처럼 새로운 종류의 작업을 쉽게 추가하고 객체 여러개에 대해서도 작업을 수행할 수 있게 작업 대상과 항목을 분리한 구조를 방문자 패턴이라고 합니다. 방문자 패턴을 이용하면 다양한 인터페이스를 가진 여러 개의 객체에 대해 각 자료형에 맞는 작업을 수행하기 유용합니다.

728x90

+ Recent posts