언어 자료구조 알고리즘/Escort C++

[C++] OOP 프로그래밍 실습 - 상세 구현하기

언제나휴일 2016. 4. 15. 15:55
반응형

10.5.3 상세 구현하기

 

 상세 구현하기에서는 시나리오를 보면서 지금 비어 있는 각 함수의 내부를 구현해 나가고 필요한 멤버가 있다면 추가하면서 프로그램을 작성해 봅시다.

 

 제일 먼저, 프로그램이 시작할 때 이 에이치 나라가 생성되고 초기화, 사용자 명령에 따른 동작, 종료화 과정을 수행시키는 부분을 해 봅시다. C++언어로 작성하는 콘솔 응용 프로그램은 main이라는 진입점 함수에서 시작한다는 것을 잘 알고 있습니다. 하지만 main은 특정 클래스 스코프가 아니며 EHLand는 논리적으로 보았을 때 하나의 개체만 생성이 되어 실행되어야 할 것입니다. 이를 위해 여기에서는 EHLand의 생성자와 소멸자의 접근 수준은 private으로 막아놓겠습니다. 대신 접근 수준이 public인 정적 메서드 Start를 제공하여 이곳에서 EHLand 개체를 생성하고 가동하기로 하겠습니다. 생성자에서는 초기화를 호출하게 하고 생성 후에 사용자 명령에 따른 동작을 호출, 소멸자에서 종료화 과정을 수행하도록 하겠습니다.

 

 제일 먼저 main에서는 EHLand의 정적 메서드 Start를 호출하는 부분을 추가합니다.

 

//Program.cpp 에 진입점 main 구현

#include "EHLand.h"

void main()

{

    EHLand::Start();

}

 

 그리고 EHLand.h에서는 접근 권한이 public으로 설정하여 정적 메서드 Start를 추가하고 private 으로 설정된 생성자와 초기화, 동작, 소멸자, 종료화에 대한 메서드를 추가합니다.

 

//EHLand.h 에 멤버 메서드 추가

public:

    static void Start();

    void ReplaceUnit(Unit *unit);//기존에 추가했었던 것

private:

    EHLand();

    ~EHLand();

    void Init();

    void Run();

    void Exit();

 

 이제 이들을 소스 파일에 추가하고 구현을 해 보기로 합시다. 먼저, Start메서드에서는 EHLand개체를 생성하고 이를 구동하고 소멸을 시킵니다.

 

//EHLand.cpp Start 메서드 구현

void EHLand::Start()

{

    EHLand *singleton = new EHLand();

    singleton->Run();

    delete singleton;

}

 

 EHLand 생성자에서는 초기화를 호출합니다.

 

EHLand::EHLand()

{

    Init();

}

 

 소멸자에서는 종료화를 호출합니다.

 

EHLand::~EHLand()

{

    Exit();

}

void EHLand::Init()

{

    throw "EHLand::Init을 구현하지 않았음";

}

void EHLand::Run()

{

    throw "EHLand::Run을 구현하지 않았음";

}

void EHLand::Exit()

{

    throw "EHLand::Exit을 구현하지 않았음";

}

 

 이제 EHLand의 초기화부터 하나하나 구현해 보기로 합시다.

 

초기화에서는 유닛 공장이 만들고 유닛의 복귀를 도와줄 ComeBackHelper개체를 생성하고 주거지와 공연장을 만들면 됩니다. 그리고 이들은 EHLand의 다른 메서드에서도 계속 사용되어야 할 것이기 때문에 이들을 관리하기 위한 멤버 필드가 필요할 것입니다. 추가로 EHLand에서 유닛들을 Template에서 만들었던 Arr 클래스를 이용하기로 하겠습니다. 이를 위해 EHLand.h에서는 Arr.h를 포함해야 할 것입니다.

 

//EHLand.h에 추가

#include "Arr.h"

Arr.h에 대해서는 별도로 설명하지 않겠습니다.

 

Arr.h

#pragma once

template <typename T>

class Arr

{

    T *base;

    const int bsize;

public:

    Arr(int _bsize):bsize(_bsize)

    {

        base = new T[bsize];

        for(int i=0 ; i<bsize ; i++)

        {

            base[i] = 0;

        }

    }

    ~Arr()

    {

        delete[ ] base;

    }

    T &operator[](int index)

    {

        if((index>=0)&&(index<bsize))

        {

            return base[index];

        }

        throw "잘못된 인덱스를 사용하였습니다.";

    }

};

  

 멤버 필드는 클래스의 시작부에 선언하여 디폴트 접근 수준인 private으로 지정을 하는 습관을 들여 정보 은닉을 통한 신뢰성을 강화하는 것이 바람직할 것입니다. 외부 스코프나 파생된 곳에서 이들의 상태를 바꿔야 한다면 필요한 수준의 접근 권한이 있는 메서드를 제공하여 상태를 바꾸도록 하면 됩니다.

 

//EHLand.h에 멤버 필드 추가

#include "Arr.h"

class EHLand

{

    UnitFactory *unitfactory;

    ComeBackHelper *comebackhelper;

    Place *places[2];

    Arr<Unit *> *units;

 

 Init메서드에서는 유닛 공장과 복귀를 위해 필요한 ComeBackHelper 개체, 공연장과 주거지, 그리고 유닛들을 보관하기 위한 템플릿 컬렉션 클래스인 Arr개체를 생성을 하는 코드를 추가합니다.

 

//EHLand.cppInit 메서드 구현

void EHLand::Init()

{

    unitfactory = new UnitFactory();

    comebackhelper = new ComeBackHelper(this);

    places[0] = new Hall(comebackhelper);

    places[1] = new Village(comebackhelper);

    units = new Arr<Unit *>(10);

}

 

 그리고 ComeBackHelper에 입력 인자를 보관하는 멤버 필드를 선언하고 생성자에서 넘어온 인자를 대입하는 부분을 추가하도록 합시다. 해당 클래스는 EHLand 헤더 파일에 작성하였습니다.

 

class ComeBackHelper:    public ComeBackUnitEvent

{

    EHLand *owner;

public:

    ComeBackHelper(EHLand *owner)

    {

        this->owner = owner;

    }

    void operator() (Unit *unit)

    {

        throw "ComeBackHelper::operator() 를 구현하지 않았음";

    }

};

 

 각 장소를 생성할 때에는 입력 인자로 유닛을 EHLand로 보내는 데 필요한 개체를 받고 있는데 이 부분은 장소에 상관없이 같아서 Place에 멤버 필드를 선언하고 Place 생성자에서 대입하면 됩니다. 그리고 Place에서 파생 받은 Hall Village의 생성자는 특별히 할 것이 없으므로 예외를 발생시켰던 구문을 삭제하기만 하면 됩니다.

 

//Place.h Place 클래스에 멤버 필드 추가

ComeBackUnitEvent *cbu_eventhandler;

 

//Place.cpp에 생성자 구현

Place::Place(ComeBackUnitEvent *cbu_eventhandler)

{

    this->cbu_eventhandler = cbu_eventhandler;        

}

 

 C++로 프로그래밍할 때 동적으로 생성한 개체의 소멸에 관한 책임을 등한시 하는 경우가 많습니다. 이에 관한 책임을 다하지 않는다고 해도 프로그램이 오류가 발생하거나 잘못된 부분을 개발자가 인지하지 못하기 때문에 간과하고 넘어갈 수 있습니다. 하지만, 만약 우리가 작성하는 프로그램이 365 24시간 계속 운영되는 서버 프로그램이라고 한다면 동적으로 생성한 개체의 소멸에 관한 책임을 다 하지 않는다면 메모리 누수로 메모리 폴트 현상이 발생할 수 있습니다. 우리는 습관적으로 개체를 생성할 때 소멸에 대한 코드도 같이 작성하는 습관을 들인다면 효과적으로 메모리 관리를 할 수 있을 것입니다. 여기서도 Exit부분에 생성한 개체들을 소멸하는 코드를 추가하도록 합시다.

 

void EHLand::Exit()

{

    delete unitfactory;

    delete comebackhelper;

    delete places[0];

    delete places[1];

    delete units;

}

 

 이제 사용자 명령에 따라 동작하는 EHLand 클래스의 Run 메서드를 구현합시다. 여기에서는 종료 메뉴를 선택하기 전까지 선택한 메뉴를 수행하는 것을 반복하기로 하였고 메뉴에는 유닛 생성, 초점 이동, 유닛 이동, 전체 상황 보기, 종료 메뉴가 있습니다. 이를 위해 7.4 함수 개체에서 사용했던 MyGlobal 클래스를 프로젝트에 추가하여 사용하기로 하겠습니다. 여기에서는 이에 대한 별도의 설명은 생략하도록 하겠습니다. 그리고, 이처럼 추가한 MyGlobal.h Unit.h에서 포함함으로써 모든 곳에서 사용할 수 있게 하겠습니다. 먼저, Run 에서는 반복문의 조건에서 메뉴를 선택하고 선택한 값이 종료 키가 아닐 동안 반복하게 지정하도록 하겠습니다. 그리고 선택한 값에 따라 선택문을 이용하여 유닛 생성, 초점 이동, 유닛 이동, 전체 상황 보기를 호출합시다.

 

//EHLand.h

    MyGlobal::KeyData SelectMenu();

    void MakeUnit();

    void MoveFocus();

    void MoveUnit();

    void ViewState();

 

//EHLand.cpp

void EHLand::Run()

{

    MyGlobal::KeyData key = MyGlobal::ESC;

    while( ( key = SelectMenu())!= MyGlobal::ESC)

    {

        switch(key)

        {

        case MyGlobal::F1: MakeUnit(); break;

        case MyGlobal::F2: MoveFocus(); break;

        case MyGlobal::F3: MoveUnit(); break;

        case MyGlobal::F4: ViewState(); break;

        default: cout<<"잘못된 메뉴를 선택하였습니다."<<endl;

    }

    cout<<"아무키나 누르세요"<<endl;

    MyGlobal::GetKey();

    }

}

MyGlobal::KeyData EHLand::SelectMenu()

{

    system("cls");

    cout<<"EHLand 메뉴  [ESC]:프로그램 종료"<<endl;

    cout<<"[F1]:유닛 생성[F2]:초점 이동[F3]:유닛 이동[F4]:전체 상태 보기"<<endl;

    cout<<"메뉴를 선택하세요"<<endl;

    return MyGlobal::GetKey();

}

void EHLand::MakeUnit()

{

    throw "EHLand::MakeUnit을 구현하지 않았음";

}

void EHLand::MoveFocus()

{

    throw "EHLand::MoveFocus를 구현하지 않았음";

}

 

 

void EHLand::MoveUnit()

{

    throw "EHLand::MoveUnit을 구현하지 않았음";

}

void EHLand::ViewState()

{

    throw "EHLand::ViewState를 구현하지 않았음";

}

 

 유닛 생성에 대하여 구현해 보기로 합시다.

 

 유닛 생성에서는 EHLand에서 최종 사용자로부터 생성할 유닛 종류와 유닛의 이름을 입력받는 것이 필요합니다. 그리고, 이를 UnitFactory에게 유닛 생성을 요청하면 UnitFactory에서는 요청한 유닛 종류에 따라 마법사나 예술가, 회사원 개체를 생성하여 생성된 유닛을 반환해 주면 됩니다. 그리고 UnitFactory에서 유닛을 생성할 때에는 공장에서 유닛의 일련번호를 순차적으로 부여하여야 합니다.

 

 EHLand MakeUnit에서는 최종 사용자에게 생성할 유닛의 종류와 이름을 입력받아 unitfactory를 통해 유닛을 생성하고 생성한 유닛을 배치해주면 될 것입니다. 유닛을 배치시키는 부분은 이미 ReplaceUnit이라는 메서드를 추가하였기 때문에 이 부분을 구현하면 될 것입니다.

 

void EHLand::MakeUnit()

{

    int utype=0;

    cout<<"생성할 유닛 종류를 입력하세요. [1]:마법사 [2]:예술가 [3]:회사원"<<endl;

    utype = MyGlobal::GetNum();

    if((utype>=1)&&(utype<=3))

    {

        cout<<"생성할 유닛의 이름을 입력하세요"<<endl;

        string name = MyGlobal::GetStr();

        Unit *unit = unitfactory->MakeUnit(utype,name);                      

        ReplaceUnit(unit);

    }

    else

    {

        cout<<"잘못 입력하셨습니다."<<endl;

    }

}

 

 ReplaceUnit에서는 유닛을 보관하는 컬렉션 변수 units의 각 원소에 보관된 값이 있는지 확인해서 빈 곳에 보관하면 될 것입니다. 여기서 주의할 점은 units변수는 형식에 대한 포인터라는 점입니다.

 

void EHLand::ReplaceUnit(Unit *unit)

{

    for(int i = 0; i<10; i++)

    {

        if((*units)[i]==0)

        {

            (*units)[i] = unit;

            break;

        }

    }

}

 

 UnitFactory MakeUnit 메서드에서는 요청에 따라 Unit에서 파생된 형식 개체를 생성하고 반환해 주어야 합니다. 그리고 자신이 생성한 Unit 개체들을 자신이 소멸 시에 같이 소멸시켜야 하므로 이들의 위치를 보관해야 합니다. 여기에서도 생성한 Unit들을 보관하기 위해 컬렉션이 필요할 것입니다.

 

 이에 헤더 파일에 Arr.h를 추가하고 유닛을 보관할 수 있는 컬렉션 변수 units을 클래스 내에 선언하고 생성자에서 생성하고 소멸자에서 해당 컬렉션도 소멸시키는 코드도 추가하겠습니다.

 

//UnitFactory.h

#include "Arr.h"

class UnitFactory

{

    Arr<Unit *> *units;

    int u_seq;

 

//UnitFactory.cpp

#include "UnitFactory.h"

UnitFactory::UnitFactory()

{

    units = new Arr<Unit *>(10);

    u_seq = 0;

}

 

UnitFactory::~UnitFactory()

{

    for(int i = 0; i<u_seq; i++)

    {

        delete (*units)[i];

    }

    delete units;

}

 

Unit *UnitFactory::MakeUnit(int utype,string uname)

{

    Unit *unit=0;

    if(u_seq < 10)

    {

        switch(utype)

        {

        case 1: unit =new Magacian(u_seq+1,name); break;

        case 2: unit =new Artist(u_seq+1,name); break;

        case 3: unit =new Worker(u_seq+1,name); break;

        }

    }

    if(unit)

    {

        (*units)[u_seq] = unit;

        u_seq++;

    }

    return unit;

}

 

 이제 유닛들의 생성자에 대해 구현해 봅시다. Unit에서 파생된 클래스들의 생성자에서는 이미 초기화를 통해 Unit의 생성자를 호출하고 있기 때문에 각 파생된 클래스에서 추가할 사항은 없습니다. 우리는 Unit 클래스에 생성자에서 입력 인자로 넘어온 일련번호와 이름을 멤버 필드에 대입하는 구문을 추가하면 됩니다. 그리고 일련번호는 상수화 멤버 필드로 하기로 합시다.

 

//Unit.h

    const int seq;

    string name;

 

//Unit.cpp

Unit::Unit(int _seq,string _name):seq(_seq),name(_name)

{

}

 

 Unit에서 파생된 Magician, Artist, Worker 클래스의 각 생성자에서는 별도로 구현할 것이 없으므로 예외를 발생하는 구문을 없애줍시다. 그리고 Unit 클래스와 파생된 Magician, Artist, Worker 클래스의 소멸자도 구현할 부분이 없으니 예외를 발생하는 구문을 없애기 바랍니다.

 

 이번에는 EHLand MoveFocus메서드를 구현해 봅시다. 여기에서는 최종 사용자에게 장소를 선택하게 한 후에 해당 장소의 SetFocus를 호출하면 됩니다. 장소를 선택하기 위한 부분은 별도의 메서드(SelectPlace)로 분리하여 구현하겠습니다. 함수는 논리적으로 하는 일이 여러 개로 구성되었다고 생각이 되면 분리하여 구현하였을 때 재사용성 및 유지 보수가 쉬워집니다.

 

void EHLand::MoveFocus()

{

    cout<<"초점 이동 메뉴입니다.

    Place *place = SelectPlace();

    if(place)

    {

        place->SetFocus();

    }

    else

    {

        cout<<"잘못 입력하셨습니다."<<endl;

    }

}

 

Place *EHLand::SelectPlace()

{

    int pnum=0;

    cout<<"장소를 선택하세요. [1]:공연장 [2]:주거지"<<endl;

    pnum = MyGlobal::GetNum();

    if((pnum >=1)&&( pnum <=2))

    {

        return places[pnum -1];

    }

    return 0;

}

 

물론, SelectPlace 메서드는 EHLand.h 파일 EHLand 클래스에 선언해 주어야 할 것입니다.

 

//EHLand.h EHLand 클래스 내부

Place *SelectPlace();

 

 EHLand MoveUnit 메서드를 구현해 보기로 합시다. 여기에서는 유닛을 선택하고 장소를 선택한 후에 해당 장소로 유닛을 보내기 위해 InsertUnit 메서드를 호출하면 됩니다. EHLand에서는 유닛을 선택하기 위한 메서드를 추가하기로 하고 이를 MoveUnit에서 호출하는 것으로 하겠습니다. 장소를 선택하는 것은 이미 구현된 SelectPlace를 호출하면 되고 특정 장소로 보내기 위한 InsertUnit은 이미 추가되어 있습니다. 그리고 마지막으로 EHLand에서 해당 유닛을 보관하는 것을 해제하기 위해 Remove메서드를 EHLand에 추가하기로 하겠습니다.

 

//EHLand.h EHLand 클래스에 추가

Unit *SelectUnit();

void Remove(Unit *unit);

 

void EHLand::MoveUnit()

{

    cout<<"이동할 유닛을 선택해주세요"<<endl;

    Unit *unit = SelectUnit();

    if(unit!=0)

    {

        cout<<"이동할 장소를 선택해주세요"<<endl;

        Place *place = SelectPlace();

        if(place != 0)

        {

            place->InsertUnit(unit);

            Remove(unit);

        }

        else

        {

            cout<<"장소를 잘못 선택하였습니다."<<endl;

        }

    }

    else

    {

        cout<<"유닛을 잘못 선택하였습니다."<<endl;

    }

}

 

 새롭게 추가된 SelectUnit 메서드와 Remove 메서드를 먼저 구현을 하고 난 후에 Place InsertUnit 메서드를 구현하기로 합시다.

 

 SelectUnit에서는 EHLand에 있는 유닛의 정보를 보여주고 나서 최조 사용자에게 유닛의 일련번호를 입력받아 해당 일련번호를 갖는 유닛을 반환하면 됩니다. 유닛의 정보를 보여주는 부분은 개체 출력자를 이용하기로 하였는데 유닛의 일련번호를 얻어오는 부분은 미처 시퀀스 다이어그램에는 나타내지 못했습니다. 이처럼 실제 프로그래밍을 하다 보면 각 단계에서 완벽하게 논리를 전개하여 다음 단계로 가지 않는 경우가 많습니다. 이러한 이유로 우리가 약속한 문서들은 언제나 완성된 것이 아니고 완성되어 나가는 것입니다. 그리고 이처럼 잘못된 것을 알게 되었다면 바로 약속된 문서를 변경함으로써 효과적으로 프로그래밍을 하시기 바랍니다.


수정한 시퀀스 다이어그램

[그림 10.23]

 

 SelectUnit 메서드는 다음과 같이 수정된 시퀀스를 반영하였습니다. 여기에서도 전체 유닛의 정보를 보여주 ViewUnits 메서드와 특정 시퀀스 번호의 유닛을 찾아주는 FindUnit 메서드로 분리하여 이를 이용하여 구현하였습니다.

 

Unit *EHLand::SelectUnit()

{

    ViewUnits();

    cout<<"유닛의 일련번호를 입력해주세요"<<endl;

    int useq = MyGlobal::GetNum();

    return FindUnit(useq);

}

 

물론, 이들은 EHLand.h에 추가하여야 합니다.

 

//EHLand.h EHLand 클래스 내부에 선언

    void ViewUnits();

    Unit *FindUnit(int useq);

 

 

 

 전체 유닛을 보여주는 ViewUnit에서는 유닛을 보관하는 컬렉션 변수 units의 각 원소를 출력하면 되는데 보관되지 않은 빈 곳이 있을 수 있기 때문에 이에 대한 부분을 제외하여 출력해야 합니다.

 

void EHLand::ViewUnits()

{

    for(int i = 0; i<10; i++)

    {

        if((*units)[i])

        {

            Unit *unit = (*units)[i];

            cout<<unit<<endl;

        }

    }

}

 

 마찬가지로 FindUnit에서도 유닛들이 보관된 컬렉션 변수 units의 각 원소에 있는 유닛들의 일련번호를 얻어와서 입력 인자로 전달받은 것과 같은 유닛을 찾아서 반환하면 됩니다. 물론, 비어 있는 유닛은 제외해야 할 것입니다.

 

Unit *EHLand::FindUnit(int useq)

{

    for(int i = 0; i<10; i++)

    {

        if((*units)[i])

        {

            Unit *unit = (*units)[i];

            if(unit->GetSeq() == useq)

            {

                return unit;

            }

        }

    }

    return 0;

}

 

 이번에는 유닛의 개체 출력자 부분과 GetSeq메서드 부분을 구현해야겠네요. 이미 유닛의 개체 출력자에서는 유닛의 View 메서드를 호출하는 것으로 구현되어 있으므로 View 메서드를 구현하면 됩니다.

 

void Unit::View(ostream &os)const

{

    cout<<"일련번호:"<<seq<<" 이름:"<<name<<endl;

}

 

 GetSeq 메서드에서는 자신의 일련번호를 반환하는 코드면 충분하겠네요.

 

int Unit::GetSeq()const

{

    return seq;

}

 

 

 이번에는 MoveUnit에서 사용한 Place InsertUnit메서드를 구현해 봅시다. 여기에서도 들어온 유닛을 보관하기 위해서는 컬렉션이 필요하겠네요. Place의 생성자에 유닛을 보관하는 컬렉션 개체를 생성하는 코드를 추가를 먼저 합시다.

 

//Place.h에 추가

#include "Arr.h"

class Place

{

    Arr<Unit *> *units;

 

//Place.cpp의 생성자 메서드 수정

Place::Place(ComeBackUnitEvent *cbu_eventhandler)

{

    this->cbu_eventhandler = cbu_eventhandler;

    units = new Arr<Unit *>(10);

}

 

 이제 InsertUnit 메서드를 구현합시다. 여기에서도 EHLand ReplaceUnit과 같이 빈 자리를 찾아 보관하면 되겠네요.

 

void Place::InsertUnit(Unit *unit)

{

    for(int i = 0; i<10; i++)

    {

        if((*units)[i]==0)

        {

            (*units)[i] = unit;

            break;

        }

    }

}

 

 

 유닛 이동을 위한 MoveUnit에서 사용하는 메서드들이 Remove를 제외하고 모두 구현하였습니다. 이번에는 Remove 메서드를 구현해 봅시다. Remove는 입력 인자로 전달된 유닛과 같은 유닛이 보관된 위치를 비게 하면 됩니다.

 

void EHLand::Remove(Unit *unit)

{

    for(int i = 0; i<10; i++)

    {

        if((*units)[i] == unit)

        {

            (*units)[i] = 0;

            return;

        }

    }

}

 

 EHLand의 마지막 메뉴인 전체 상황 보기를 구현해 보기로 합시다. 여기에서는 EHLand에 있는 모든 유닛을 보여주고 각 장소를 개체 출력자를 이용하여 보여주면 됩니다. 이미 EHLand에 있는 모든 유닛의 정보를 보여주는 메서드는 ViewUnits 로 구현을 하였으니 이를 호출하면 됩니다.

 

void EHLand::ViewState()

{

    ViewUnits();

    cout<<places[0]<<endl;

    cout<<places[1]<<endl;

}

 

 장소의 개체 출력자에서도 Place의 가상 메서드 View를 호출하는 것으로 구현하였으니 여기에서는 Place View 메서드와 각 장소에서 View를 재정의하면 됩니다. Place에서는 해당 장소에 있는 유닛의 정보를 보여주는 부분을 구현하고 각 장소에서는 어디인지를 출력한 후에 기반 클래스인 Place에 있는 무효화 된 View 메서드를 호출해서 구현하기로 합시다.

 

void Place::View(ostream &os)const

{

    for(int i = 0; i<10; i++)

    {

        if((*units)[i])

        {

            cout<<(*units)[i]<<endl;

        }

    }

}

 

void Hall::View(ostream &os)const

{

    cout<<"공연장"<<endl;

    Place::View(os);

}

 

void Village::View(ostream &os)const

{

    cout<<"마을"<<endl;

    Place::View(os);

}

 

 현재 EHLand에서의 메뉴들에 대하여 구현하였습니다. 그리고 UnitFactory에서는 유닛을 생성과 소멸에 관한 책임만을 하는 클래스인데 이 부분도 구현하였습니다.

 

 이제는 Hall Village에 포커스가 왔을 때 해야 할 작업들을 구현해 보기로 합시다.

 

 먼저, Hall SetFocus를 구현해 봅시다. Hall SetFocus EHLand Run 메서드처럼 사용자로부터 메뉴를 입력받아 선택된 동작을 하는 것을 반복합니다. 메뉴로는 공연 관람하기, 무대로 올라가기, 유닛이 에이치 나라로 복귀하기가 있습니다. 공연장 초점 종료를 선택하면 반복문을 빠져나가게 하여 SetFocus 함수를 끝내면 이를 호출한 EHLand로 가기 때문에 별다른 구현을 할 부분은 없습니다.

 

void Hall::SetFocus()

{

    MyGlobal::KeyData key = MyGlobal::ESC;

    while( ( key = SelectMenu())!= MyGlobal::ESC)

    {

        switch(key)

        {

            case MyGlobal::F1: ViewConcert(); break;

            case MyGlobal::F2: GoOnStage(); break;

            case MyGlobal::F3: ComeBackUnit(); break;

            default: cout<<"잘못된 메뉴를 선택하였습니다."<<endl;

        }

       cout<<"아무키나 누르세요"<<endl;

       MyGlobal::GetKey();

    }

}

 

 SelectMenu ViewConcert, GoOnState Hall에 추가해야 할 메서드이고 ComeBackUnit은 모든 장소에서 공통으로 동작하므로 Place에 구현하면 됩니다. 이를 위해 먼저 각 메서드를 Place.h Hall.h에 선언을 추가하고 하나씩 구현해 봅시다. ComeBackUnit은 파생된 장소에서 접근할 메서드이기 때문에 protected로 접근 설정하십시요.

 

//Place.h

protected:

    void ComeBackUnit();

 

//Hall.h

    MyGlobal::KeyData SelectMenu();

    void ViewConcert();

    void GoOnStage();

 

 SelectMenu는 단순히 메뉴를 출력하고 최종 사용자로부터 메뉴를 입력받아 입력받은 키를 반환하면 되겠네요.

 

MyGlobal::KeyData Hall::SelectMenu()

{

    system("cls");

    cout<<"Hall 메뉴  [ESC]:EHLand로 초점 복귀"<<endl;

    cout<<"[F1]:공연보기 [F2]:무대로 올라가기 [F3]:EHLand로 유닛 복귀"<<endl;

    cout<<"메뉴를 선택하세요"<<endl;

    return MyGlobal::GetKey();

}

 

 ViewConcert에서는 모든 유닛에 공연을 관람하게 하고 예술가인 경우에만 논평하게 하면 됩니다. 그런데, 유닛을 보관하는 컬렉션은 Place의 멤버 필드로 접근 설정이 private으로 되어 있습니다. Hall에서 이들에게 명령을 내리기 위해서는 유닛을 얻어올 수 있는 메서드를 Place에서 제공하고 접근 설정은 protected로 해야합니다.

 

 그리고 Hall에서는 Unit에게 IPlay에 있는 행위만을 명령하게 하고 Village에서는 IRelax에 있는 행위만을 명령할 수 있게 하기로 하였습니다. 이를 위해 Place에서 Hall에서 사용하기 위해 특정 인덱스에 있는 IPlay를 반환하는 메서드와 Village에서 사용하기 위해 IRelax를 반환하는 메서드를 제공하기로 합시다. 그리고 이들의 메서드 이름은 IndexAt으로 정하기로 하겠습니다. 대신 이들을 사용할 때 필요한 인자는 인덱스로 모두 같으므로 이를 구분하기 위해 스텁 매개 변수를 사용하겠습니다. 그리고, 이 두 개의 메서드는 반환 형식을 제외하고는 같은 논리로 구현이 되기 때문에 Place 내에 접근 설정이 private IndexAt도 구현하겠습니다.

 

//Place.h에 추가

protected:

    IPlay *IndexAt(int index,bool)const;

    IRelax *IndexAt(int index,int)const;

private:

    Unit *IndexAt(int index)const;

 

//Place.cpp

IPlay *Place::IndexAt(int index,bool)const

{

    return IndexAt(index);

}

IRelax *Place::IndexAt(int index,int)const

{

    return IndexAt(index);

}

Unit *Place::IndexAt(int index)const

{

    if((index>=0)&&(index<10))

    {

        if((*units)[index])

        {

            return (*units)[index];

        }

    }

    return 0;

}

 

 

 이제 공연장의 ViewConcert를 구현해 봅시다. 여기에서는 IndexAt을 통해 IPlay를 얻어와서 얻어온 IPlay의 공연 보기를 호출하고 해당 개체가 Artist인지 확인하기 위해 dynamic_cast를 이용하여 바르면 비판하면 됩니다.

 

void Hall::ViewConcert()

{

    IPlay *iplay = 0;

    Artist *artist = 0;

    for(int i = 0;i<10;i++)

    {

        iplay =IndexAt(i,true);

        if(iplay)

        {

            iplay->ViewConcert();

            artist = dynamic_cast<Artist *>(iplay);

            if(artist)

            {

                artist->Criticism();

            }

        }

    }

}

 

 이제 Artist Criticism 메서드를 구현해 봅시다. 여기에서는 단순히 공연을 비판하다가 호출했음을 확인할 수 있게 화면에 출력하는 것만 하기로 하겠습니다.

 

void Artist::Criticism()

{

    cout<<this<<endl;

    cout<<"이번 공연은 이렇고 저렇고....."<<endl;

}

 

 

 그리고 ViewConcert를 하면 모든 유닛은 할 때 행복을 느끼고 마법사는 추가로 매직 아이를 하기로 하였습니다. 이 또한 단순히 화면에 출력하는 것으로 하겠습니다. Magician에서는 무효화 된 기반 클래스인 Unit 클래스의 ViewConcert를 호출한 후에 매직 아이를 하는 것을 출력하면 되겠네요.

 

void Unit::ViewConcert()

{

    View();

    cout<<"공연을 보니 행복해......"<<endl;

}

 

void Magician::ViewConcert()

{

    Unit::ViewConcert();

    cout<<"매직 아이......"<<endl;

}

 

 GoOnStage 메서드를 구현해 봅시다. 여기에서는 해당 장소에 있는 유닛을 선택하기 위해 유닛 정보를 출력하고 유닛의 이름을 입력받기로 하였습니다. 이를 위해서는 다시 유닛에 자신의 이름을 반환하는 메서드를 제공하여야 할 것입니다. 이에 대한 부분도 시퀀스 다이어그램을 수정하는 것부터 하겠습니다.


수정한 시퀀스 다이어그램

[그림 10.24]

 

 유닛을 선택하는 것도 Village에서 휴식하기에서도 필요하므로 Place에서 제공하기로 하겠습니다. 특정 인덱스에 있는 유닛을 얻어오기 위해 했던 방식과 같게 합시다.

 

//Place.h

protected:

    IPlay *SelectUnit(bool)const;

    IRelax *SelectUnit(int)const;

private:

    Unit *SelectUnit()const;

 

//Place.cpp

Unit *Place::SelectUnit()const

{

    View();

    cout<<"유닛의 이름을 입력하세요"<<endl;

    string name = MyGlobal::GetStr();

 

    Unit *unit = 0;

    for(int index = 0;index<10;index++)

    {

        unit = (*units)[index];

        if(unit)

        {

            if(unit->GetName() == name)

            {

                return (*units)[index];

            }

        }

    }

    return 0;

}

 

 Unit 클래스에 GetName 메서드를 선언 및 구현해야겠지요.

 

//Unit.h

string GetName()const;

 

//Unit.cpp

string Unit::GetName()const

{

    return name;

}


 이제 실제로 GoOnState메서드를 구현하면 되겠네요.

 

void Hall::GoOnStage()

{

    cout<<"무대로 올라갈 유닛을 선택해주세요"<<endl;

    IPlay *iplay = SelectUnit(true);

    if(iplay)

    {

        iplay->Introduce();

    }

    else

    {

        cout<<"잘못 선택하였습니다."<<endl;

    }

}

 

 유닛을 소개하는 것은 유닛 종류에 상관이 없이 자기 번호와 이름을 소개하는 것으로 하기로 하였습니다. 이 또한 단순히 화면에 출력하는 것으로 하겠습니다.

 

void Unit::Introduce()

{

    cout<<"제가 무대에 올라왔으니 소개를 하지요."<<endl;

    View();

}

 

 공연장의 마지막 메뉴인 유닛 EHLand로 복귀시키는 ComeBackUnit을 구현해 봅시다. ComeBackUnit 메서드에서는 먼저 복귀할 유닛을 선택한 후 복귀시키고 컬렉션에서 Remove시키면 될 것입니다.

 

void Place::ComeBackUnit()

{

    cout<<"EHLand로 복귀할 유닛을 선택해주세요"<<endl;

    Unit *unit = SelectUnit();

    if(unit)

    {

        (*cbu_eventhandler)(unit);

        Remove(unit);

    }

    else

    {

        cout<<"잘못 선택하였습니다."<<endl;

    }

}

 

 Remove 메서드는 EHLand에서의 Remove와 같은 방법으로 구현할 수 있을 것입니다.

 

//Place.h

void Remove(Unit *unit);

 

//Place.cpp

void Place::Remove(Unit *unit)

{

    for(int i = 0; i<10; i++)

    {

        if((*units)[i] == unit)

        {

            (*units)[i] = 0;

            return;

        }

    }

}

 

 그리고 ComeBackHelper에 함수 호출 연산자 중복 정의를 구현하여야 할 것입니다. 여기에서는 EHLand 개체의 ReplaceUnit메서드를 호출하면 되겠네요.

 

 class ComeBackHelper:

    public ComeBackUnitEvent

{

    EHLand *owner;

public:

    ComeBackHelper(EHLand *owner)

    {

        this->owner = owner;

    }

    void operator() (Unit *unit)

    {

        owner->ReplaceUnit(unit);

    }

};

 

 이제 주거지에 초점이 왔을 때의 수행할 기능들을 구현해 보기로 합시다. 대부분 공연장을 구현했을 때의 원리와 비슷합니다.

 

 먼저, SetFocus 부분인데 전체 구동 원리는 Hall SetFocus 메서드와 같습니다. 단지 선택해서 호출할 메서드가 무엇인지만 차이가 있습니다.

 

void Village::SetFocus()

{

    MyGlobal::KeyData key = MyGlobal::ESC;

    while( ( key = SelectMenu())!= MyGlobal::ESC)

    {

        switch(key)

        {

        case MyGlobal::F1: TurnOff(); break;

        case MyGlobal::F2: Relax(); break;

        case MyGlobal::F3: ComeBackUnit(); break;

        default: cout<<"잘못된 메뉴를 선택하였습니다."<<endl;

        }

        cout<<"아무키나 누르세요"<<endl;

        MyGlobal::GetKey();

    }

}

 

 Village.h SetFocuse에서 호출하는 메서드들을 선언하고 각 메서드를 구현해 봅시다. ComeBackUnit은 기반 클래스인 Place에서 구현된 메서드를 호출하는 것이기 때문에 별도의 선언과 구현이 필요 없습니다.

 

//Village.h

private:

    MyGlobal::KeyData SelectMenu();

    void TurnOff();

    void Relax();

 

//Village.cpp

MyGlobal::KeyData Village::SelectMenu()

{

    system("cls");

    cout<<"Village 메뉴  [ESC]:EHLand로 초점 복귀"<<endl;

    cout<<"[F1]:소등 [F2]:휴식 [F3]:EHLand로 유닛 복귀"<<endl;

    cout<<"메뉴를 선택하세요"<<endl;

    return MyGlobal::GetKey();

}

 

 

 TurnOff 메서드에서는 전체 유닛을 잠을 자게 하면 됩니다. 다만, 해당 유닛이 Worker일 경우에는 자기 전에 알람을 설정해야 합니다. 여기에서도 공연장의 ViewConcert 메서드처럼 기반 클래스인 Place IndexAt을 통해 IRelax를 얻어와서 해당 개체가 Worker인지 확인하기 위해 dynamic_cast를 이용하여 알람을 설정하고 해당 유닛을 잠을 자게 하려면 얻어온 IRelax Sleep 메서드를 호출하면 됩니다.

 

void Village::TurnOff()

{

    IRelax *irelax = 0;

    Worker *worker = 0;

    for(int i = 0;i<10;i++)

    {

        irelax =IndexAt(i,0);

        if(irelax)

        {

            worker = dynamic_cast<Worker *>(irelax);

            if(worker)

            {

                worker->SetAlram();

            }

            irelax->Sleep();

        }

    }

}

 

 Unit은 잠을 잘 때의 구체적 행위가 모두 다르므로 파생된 각 클래스에서 Sleep 메서드를 재정의를 하기로 하였습니다. 여기서도 간단히 화면에 정보를 출력하는 형태로 구현하겠습니다.

 

void Artist::Sleep()

{

    View();

    cout<<"노래는 즐겁다......"<<endl;

}

 

void Magician::Sleep()

{

    View();

    cout<<"꿈속으로......"<<endl;

}

 

void Worker::Sleep()

{

    View();

    cout<<"열심히 일한 당신, 드르렁~ 드르렁~";

}

 

void Worker::SetAlram()

{

    View();

    cout<<"내일 아침을 위해 알람설정"<<endl;

}

 

 Village Relax 메서드를 구현해 봅시다. Hall GoOnStage 처럼 Place SelectUnit을 통해 IRelax 개체를얻어와서 휴식을 취하게 하면 됩니다. 그리고 해당 개체가 마법사인지 dynamic_cast를 이용하여 확인 후에 달나라 여행을 보내면 되겠네요.

 

void Village::Relax()

{

    cout<<"휴식할 유닛을 선택해주세요"<<endl;

    IRelax *irelax = SelectUnit(0);

    if(irelax)

    {

        irelax->Relax();

        Magician *magician = dynamic_cast<Magician *>(irelax);

        if(magician)

        {

            magician->TravelMoon();

        }

    }

    else

    {

        cout<<"잘못 선택하였습니다."<<endl;

    }

}

 

 

 유닛이 휴식을 취하는 것도 구체적 행위가 모두 다르므로 파생된 클래스에서 재정의하여 구현하기로 하였습니다. 마찬가지로 화면에 정보를 출력하는 수준으로 간단히 구현하기로 합시다.

 

void Artist::Relax()

{

    View();

    cout<<"비발디의 사계......"<<endl;

}

 

void Magician::Relax()

{

    View();

    cout<<"랄라라... 댄스! 댄스!"<<endl;

}

void Magician::TravelMoon()

{

    View();

    cout<<"여기는 고요의 바다......"<<endl;

}

 

void Worker::Relax()

{

    View();

    cout<<"몸이 피곤해...... 드르렁~ 드르렁~";

}

 

이제 구현한 EHLand을 테스트하고 디버깅 하시기 바랍니다.

 

이상으로 짧고도 길었던 Escort C++을 마치도록 하겠습니다.



10 OOP Part1

10 OOP Part2

10 OOP Part3

(모든 동영상 강의는 무료입니다.)

반응형