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

[C++] OOP 프로그래밍 실습 - 접근 권한이 public인 멤버 메서드

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

10.5.2 접근 권한이 public인 멤버 메서드

 

 관계에 따른 헤더 파일을 추가하였으면 시퀀스 다이어그램들을 보면서 접근 권한이 public인 멤버 메서드들을 추가해 보도록 합시다. 우리는 설계 단계의 시퀀스 다이어그램에서 다른 개체의 메서드를 호출하는 것에 대해서만 약속을 하였는데 호출을 당하는 개체에는 해당 시그니쳐를 갖는 멤버 메서드가 public으로 되어 있어야 할 것입니다.

 

초기화 시퀀스를 보면 UnitFactory, ComeBackHelper, Village, Hall을 생성하고 있습니다. 이들의 생성자 메서드는 접근 권한이 public으로 노출되어야 할 것입니다. 시퀀스 다이어그램을 보시면 UnitFactory의 생성자는 기본 생성자이고 ComeBackHelper의 생성자는 EHLand *를 입력 인자로 받습니다. 그리고 Village, Hall의 생성자는 ComeBackUnitEvent *형식을 입력 인자로 받고 있으며 두 장소에서 EHLand로 유닛을 복귀시키는 동작은 같기 때문에 이를 관리하는 것은 기반 클래스인 Place에서 하기로 하겠습니다. , Place의 생성자의 입력 인자도 Village Hall과 같게 하도록 하겠습니다. 이들을 각각 헤더 파일과 소스 파일에 추가하십시요.

 

// UnitFactory.h를 수정

class UnitFactory

{

public:

    UnitFactory();

};

//UnitFactory.cpp를 수정

#include "UnitFactory.h"

UnitFactory::UnitFactory()

{

    throw "UnitFactory::UnitFactory 구현하지 않았음";

}

 

 소스 파일에 멤버 메서드를 추가를 하고 아직 구현하지 않았기 때문에 이를 사용하였을 때 예외를 발생을 시켜 구현되지 않은 멤버 메서드를 호출하였을 때 이를 빠르게 발견할 수 있도록 할 수 있습니다. ComeBackHelper 클래스는 EHLand.h에 직접 구현하도록 하겠습니다. 먼저, 생성자 메서드를 시퀀스 다이어그램에 약속된 형태로 추가합시다.

 

//EHLand.h를 수정

class ComeBackHelper:

    public ComeBackUnitEvent

{

public:

    ComeBackHelper(EHLand *owner)

    {

        throw "ComeBackHelper::ComeBackHelper 구현하지 않았음";

    }

};

 

 Place 생성자와 Village, Hall의 생성자도 추가하십시오. , Place 개체는 Village Hall의 기반 클래스로 파생된 클래스인 Village Hall 개체가 생성될 때 기반 클래스의 생성자가 호출되므로 접근 권한을 protected로 설정하겠습니다.

 

//Place.h를 수정

class Place

{

protected:

    Place(ComeBackUnitEvent *cbu_eventhandler);

};

 

//Place.cpp를 수정

#include "Place.h"

Place::Place(ComeBackUnitEvent *cbu_eventhandler)

{

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

}

 

 그리고 파생된 Village Hall의 기반 클래스의 기본 생성자가 없으므로 Village Hall의 생성자에서 초기화하는 것을 추가해 주셔야 합니다. 아래와 같이 Hall.h Hall.cpp도 수정하십시오.

 

//Village.h를 수정

class Village :

    public Place

{

public:

    Village(ComeBackUnitEvent *cbu_eventhandler);

};

 

//Village.cpp를 수정

#include "Village.h"

Village::Village(ComeBackUnitEvent *cbu_eventhandler)

    :Place(cbu_eventhandler)

{

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

}

 

 이번에는 유닛 생성 시퀀스 다이어그램을 보면서 public 메서드를 추가해 봅시다. 유닛 생성에서는 EHLand에서 UnitFactory에게 유닛을 생성해 달라는 MakeUnit 메서드와 UnitFactory로부터 생성하려고 하는 개체 형식에 따라 Magician, Artist, Worker를 생성해야 합니다. 이들을 각 시퀀스 다이어그램에 약속한 것에 따라서 public 메서드를 추가해 봅시다.

 

 

 UnitFactory 에서는 MakeUnit을 추가해 봅시다.

 

//UnitFactory.h에 추가

    Unit *MakeUnit(int utype,string uname);

 

//UnitFactory.cpp에 추가

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

{

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

}

 

 이들을 추가하고 컴파일을 하면 string 형식을 모르기 때문에 컴파일 오류가 발생할 것입니다. 앞으로 프로젝트에서 사용할 std의 기본 입출력 스트림과 string에 대한 부분을 Common.h에 추가하고 이를 Unit 헤더에 포함하기로 하겠습니다. 이 프로그램에서 Unit은 모든 곳에서 포함되므로 Unit 헤더에만 포함하면 될 것입니다. 그리고, 개발하다가 기본적으로 포함해야 할 것이 있다면 이를 Common.h에 추가하시기 바랍니다.

 

Common.h

#pragma once

#include <iostream>

#include <string>

using std::ostream;

using std::cout;

using std::endl;

using std::cin;

using std::string;

 

//Unit.h에 추가

#include "Common.h"

 

 Magician Artist, Worker를 생성하고 소멸하는 것은 UnitFactory에서만 가능하게 하기로 하였습니다. 이 경우에 생성자와 소멸자를 public으로 노출을 시키면 다른 스코프에서도 이들을 생성 혹은 소멸시킬 수 있습니다. 이를 막기 위해 이들의 생성자와 소멸자는 private로 접근 수준을 결정하겠습니다. 대신 이들의 생성과 소멸을 책임지는 UnitFactory friend로 지정하도록 함으로써 전체 신뢰성을 높이겠습니다. 이와 같이 friend도 목적에 부합하게 잘 사용한다면 프로그램의 신뢰성을 떨어트리는 요소가 아닌 향상하는 요소로 사용될 수 있습니다.

 

//Magician.h를 수정

class Magician :

    public Unit

{

private:

    friend class UnitFactory;

    Magician(int seq,string name);

    ~Magician();

};

 

//Magician.cpp를 수정

Magician::Magician(int seq,string name)

    :Unit(seq,name)

{

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

}

Magician::~Magician()

{

    throw "Magician::~Magician을 구현하지 않았음";

}

 

 같은 원리로 Artist Worker도 수정하면 됩니다. 그리고 Unit 클래스의 생성자는 파생된 스코프에서 접근할 수 있어야 하기 때문에 protected로 접근 수준을 결정해야 할 것이며 소멸은 UnitFactory와 파생된 곳에서만 가능해야 하기 때문에 protected으로 지정한 후에 UnitFactory friend로 지정하면 됩니다.

 

//Unit.h를 수정

class Unit:

    public IPlay, public IRelax

{

protected:

    Unit(int seq,string name);

    friend class UnitFactory;

    virtual ~Unit();

};

 

 

//Unit.cpp를 수정

Unit::Unit(int seq,string name)

{

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

}

Unit::~Unit()

{

    throw "Unit::~Unit을 구현하지 않았음";

}

 

 포커스 이동 시퀀스 다이어그램에서는 EHLand에서 Place SetFocus 메서드를 호출하는 것만 있습니다. 다만, 각 장소의 SetFocus에서는 실제 수행하는 구체적 구현이 다르므로 순수 가상 함수로 정의하고 파생된 곳에서 재정의를 하여야 합니다.

 

//Place.h를 수정

public:

    virutal void SetFocus()=0;

 

//Hall.h를 수정

public:

    void SetFocus();

 

//Hall.cpp를 수정

void Hall::SetFocus()

{

    throw "Hall::SetFocus를 구현하지 않았음";

}

 

 같은 원리로 Village.h Village.cpp도 작성하시면 됩니다.

 

 이번에는 유닛 이동을 살펴봅시다. 유닛 이동 시퀀스 다이어그램을 보면 EHLand에서 유닛을 선택하기 위해 개체를 출력하는 것과 선택한 유닛을 Place에 보내는 부분이 있습니다. 유닛의 정보를 출력하는 것은 개체 출력자로 구현하기로 하였으므로 개체 출력자에서는 Unit View메서드를 사용하기로 하였습니다. 이를 위해 Unit 클래스에는 View 메서드를 추가하고 전역에피 연산자로 ostream & const Unit 포인터를 받는 << 연산자를 중복 정의하시면 됩니다.

 

//Unit.h에 추가

public:

    void View(ostream &os=cout)const; //Unit 클래스 내부에

 

ostream &operator<<(ostream &os,const Unit *unit); //클래스 외부에

 

 

//Unit.cpp에 추가

void Unit::View(ostream &os)const

{

    throw "Unit::View를 구현하지 않았음";

}

ostream &operator<<(ostream &os,const Unit *unit)

{

    unit->View(os);

    return os;

}

 

 Place에는 유닛이 추가되는 InsertUnit메서드를 추가하시면 됩니다.

 

//Place.h에 추가

    void InsertUnit(Unit *unit);

 

//Place.cpp에 추가

void Place::InsertUnit(Unit *unit)

{

    throw "Place::InsertUnit을 구현하지 않았음";

}

 

 EHLand에서 전체 상황 보기에서는 유닛의 정보를 개체 출력자를 이용하여 보여주고 각 장소의 정보도 개체 출력자를 이용하여 보여주면 됩니다. 이미 유닛의 개체 출력자는 추가하였으므로 장소의 개체 출력자를 추가하면 됩니다. 같은 원리로 구현한다고 할 때 장소의 View메서드는 가상 함수에서는 공통적인 부분만 구현하고 다른 부분은 Village Hall에서 추가 구현하도록 합시다.

 

//Place.h에 추가

virtual void View(ostream &os=cout)const; // Place 클래스 내부

ostream &operator<<(ostream &os,const Place *place); //Place 클래스 외부

 

//Place.cpp에 추가

void Place::View(ostream &os)const

{

    throw "Place::View를 구현하지 않았음";

}

ostream &operator<<(ostream &os,const Place *place)

{

    place->View(os);

    return os;

}

 

 

//Hall.h에 추가

void View(ostream &os)const;

 

//Hall.cpp에 추가

void Hall::View(ostream &os)const

{

    throw "Hall::View를 구현하지 않았음";

}

 

 같은 원리로 Village에도 View 메서드를 추가하시면 됩니다.

 

 Hall에서는 IPlay에 약속된 행위만 수행해야 합니다. 공연하기에서 감상하기에 대한 행위에 대하여 구현 약속을 IPlay에서 합니다. 이를 위해 IPlay에서는 ViewConcert메서드를 순수 가상 함수로 작성하면 됩니다. 그리고  공연을 감상할 때 모든 유닛은 공통으로 행복을 느끼는 부분이 있고 마법사인 경우에만 추가로 매직 아이를 하므로 공통된 부분을 Unit.h에서 정의하고 Magician에서는 이를 재정의하도록 하겠습니다.

 

//IPlay.h

public:

    virtual void ViewConcert()=0;

 

//Unit.h에 추가

    void ViewConcert();

 

//Unit.cpp에 추가

void Unit::ViewConcert()

{

    throw "Unit::ViewConcert를 구현하지 않았음";

}

 

//Magician.h에 추가

public:

    void ViewConcert();

 

//Magician.cpp에 추가

void Magician::ViewConcert()

{

    throw "Magician::ViewConcert를 구현하지 않았음";

}

 

 비판하기는 Artist에만 있는 기능이기 때문에 Artist에만 추가하면 되겠네요.

 

//Artist.h에 추가

public:

    void Criticism();

 

//Artist.cpp에 추가

void Artist::Criticism()

{

    throw "Artist::Criticism을 구현하지 않았음";

}

 

 Hall에서 유닛에게 할 수 있는 또 다른 행위로 소개하기가 있습니다. 이 또한 IPlay에서 순수 가상 함수로 만들고 이들에 대한 구체적 행위는 모든 유닛이 같기 때문에 Unit 클래스에서 구현하면 됩니다.

 

//IPlay.h에 추가

    virtual void Introduce()=0;

 

//Unit.h에 추가

    void Introduce();

 

//Unit.cpp에 추가

void Unit::Introduce()

{

    throw "Unit::Introduce를 구현하지 않았음";

}

 

 이번에는 유닛을 EHLand에 복귀시키는 부분에 대해 살펴보기로 합시다. 이에 대한 시퀀스 다이어그램을 보시면 먼저 Place에서는 복귀할 Unit을 선택하기 위해 개체의 정보를 출력하고 ComeBackUnitEvent개체의 함수 호출 연산자를 호출하면 ComeBackUnitEvent 개체에서는 EHLand ReplaceUnit을 호출하는 구조로 되어 있습니다.

 

 Unit의 개체 정보를 출력하는 부분은 이미 앞에서 제공하고 있기 때문에 별도로 추가할 부분이 없습니다.

 

 여기서 추가해야 할 것은 ComeBackUnitEvent 클래스에 함수 연산자 중복 정의를 해야 하는데 해당 클래스는 추상 클래스로 이에 대한 순수 가상 메서드만 약속을 할 것입니다. 이에 대한 구체적인 구현은 파생된 클래스인 ComeBackHelper에서 재정의하여 구현을 할 것입니다.

  

//Place.h에 추가

class ComeBackUnitEvent

{

public:

    virtual void operator() (Unit *unit)=0;

};

 

//EHLand.h에 추가

class ComeBackHelper:

    public ComeBackUnitEvent

{

public:

    ComeBackHelper(EHLand *owner)

    {

        throw "ComeBackHelper::ComeBackHelper 구현하지 않았음";

    }

    void operator() (Unit *unit)

    {

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

    }

};

 

 그리고 EHLand ReplaceUnit 메서드를 추가해야 합니다.

 

//EHLand.h

class EHLand

{

public:

    void ReplaceUnit(Unit *unit);

};

 

//EHLand.cpp

void EHLand::ReplaceUnit(Unit *unit)

{

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

}

 

 

 주거지에서 소등하기에 대한 부분을 해 봅시다. 여기도 잠을 자게 하는 행위에 대한 약속은 IRelax에서 합니다. 그리고 잠을 행위는 예술가와 마법사, 회사원 모두 다르므로 이들 클래스에서 재정의를 하면 됩니다. 그리고 Worker일 경우에는 알람을 설정할 수 있어야 하는데 이는 Worker에만 추가하면 될 것입니다.

 

//IRelax.h

public:

    virtual void Sleep()=0;

 

//Magacian.h

void Sleep();

 

//Magacian.cpp

void Magician::Sleep()

{

    throw "Magician::Sleep을 구현하지 않았음";

}

 

 Artist Worker에서도 같은 방법으로 추가합니다.

 

 알람을 설정하는 것은 Worker에만 있는 기능이기 때문에 Worker에만 추가하면 됩니다.

 

//Worker.h

void SetAlram();

 

//Worker.cpp

void Worker::SetAlram()

{

    throw "Worker::SetAlram을 구현하지 않았음";

}

 

 

 휴식하기 부분에서는 유닛을 선택하기 위해 유닛의 정보를 출력하고 선택된 유닛에게 휴식을 취하게 합니다. 다만, 마법사일 때에는 달나라 여행을 하게 합니다.

 

 휴식하는 것도 IRelax에서 순수 가상 함수로 만들고 각 유닛의 종류에 따라 다르게 동작하기 때문에 Unit에서 파생된 각 클래스에서 재정의를 하면 될 것입니다.

 

//IRelax.h

virtual void Relax()=0;

 

//Magacian.h

void Relax();

void TravelMoon();

 

//Magician.cpp

void Magician::Relax()

{

    throw "Magician::Relax를 구현하지 않았음";

}

void Magician::TravelMoon()

{

    throw "Magician::TravelMoon을 구현하지 않았음";

}

 

 디폴트 소멸자를 사용하지 않고 개발자가 추가해야 할 클래스는 내부에서 동적으로 개체를 생성하는 클래스들과 특정 클래스나 특정 함수에서만 소멸에 관한 책임을 부과하는 경우입니다. 이미 Unit Unit에서 파생한 클래스에서는 이들에 대하여 추가하였습니다. 여기에서는 유닛을 생성하는 UnitFactory의 소멸자를 추가하기로 하겠습니다.

 

//UnitFactory.h

    ~UnitFactory();

 

//UnitFactory.cpp

UnitFactory::~UnitFactory()

{

    throw "UnitFactory::~UnitFactory 구현하지 않았음";

}


10 OOP Part1

10 OOP Part2

10 OOP Part3

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

반응형