[디딤돌 C++] 72. 최종 실습 – 프로토 타이핑

이번에는 앞에서 작성한 EhNara 뼈대에 요구 분석 및 정의에서 작성한 유즈케이스 다이어그램을 보며 프로토 타이핑을 작성합시다.

GUI(Graphic User Interface) 프로그램에서는 이해관계자의 요구 사항을 제대로 파악한 것인지 확인하기 위해 프로토 타이핑을 작성하곤 합니다. 이해관계자의 요구 사항은 고정적인 것이 아니라 시시 때때로 변할 수 있는데 자주 변하는 부분이 대부분 User Interface에 관한 것이 많습니다. 즉 내부적인 것 보다 외형적인 부분에서 요구 사항이 자주 바뀔 수 있습니다. 이러한 것을 개발 앞 단계에 배치하여 이해관계자의 요구 사항을 반영하기 위해 프로토 타이핑을 합니다.

EhNara 프로그램은 GUI 프로그램이 아니라서 이해 관계자의 요구 사항을 파악하기 위한 목적은 아닙니다. 여기에서 프로토 타이핑을 작성하는 것은 기본적인 사용자와 상호 작용에 따라 기능 선택을 할 수 있게 흐름을 잡는데 목적이 있습니다.

EhNara 프로그램의 요구 분석 및 정의 단계에서는 각 장소별 유즈케이스 다이어그램까지 작성하였습니다. 따라서 프로젝트에 Place(장소), School(학교), Downtown(유흥가), Village(주거지) 클래스를 추가하세요. 물론 클래스 다이어그램을 작성하며 약속한 것처럼 일반화 관계에 맞게 추가해야겠죠.

EhNara 클래스 내부에 세 개의 Place 개체를 보관할 수 있는 멤버와 이들의 인덱스로 사용할 열거형을 정의하세요.

enum PIndex//장소 인덱스
{
    PI_SCHOOL, PI_DOWNTOWN, PI_VILLAGE, PI_MAX
};
Place *places[PI_MAX];

EhNara 초기화에서 장소 개체들을 생성하는 구문을 추가하세요.
void EhNara::Initialize()
{
    //장소 개체 생성
    places[PI_SCHOOL] = new School();
    places[PI_DOWNTOWN] = new Downtown();
    places[PI_VILLAGE] = new Village();
}

개체를 생성하는 구문을 작성하면서 해제하는 구문도 같이 작성하는 습관을 갖으면 좋아요.

void EhNara::Exit() //해제화
{
    for(int i = 0; i<PI_MAX; i++)//모든 장소 개체 해제
    {
        delete places[i];
    }
}

이제 사용자와 상호작용하는 Run 메서드를 구현할 차례입니다. 그런데 콘솔 응용 프로그램에서 메뉴를 선택할 때는 기능 키를 자주 사용합니다.

여기에서는 사용자와 상호 작용에 관해 필요한 기능을 정적 클래스 ehglobal에 작성하려고 합니다. 먼저 ehglobal 클래스를 추가하세요. ehglobal 클래스는 개체를 만들 필요가 없으니 생성자와 소멸자의 가시성은 private으로 접근 지정하세요.

class ehglobal
{
private:
    ehglobal(void)
    {
    }
    ~ehglobal(void)
    {
    }
};

ehglobal 클래스에는 콘솔 화면을 지우는 clrscr, 기능 키를 입력받는 getkey, 아무 키나 누를 때까지 대기하는 waitkey 메서드를 제공할 것입니다.

static void clrscr();         //콘솔 화면 클리어
static keydata getkey();   //기능 키 입력
static void waitkey(); //아무 키나 누를 때까지 대기

그리고 기능 키에 사용할 상수를 keydata 열거형으로 정의합시다.

enum keydata //기능 키에 사용할 상수 열거
{
    NO_DEFINED,F1,F2,F3,F4,F5,F6,F7, F8, ESC
};

먼저 콘솔 화면을 지우는 것은 system 호출에 cls 명령어를 전달합니다. cls 명령은 콘솔 화면을 지우는 명령입니다. 그리고 system 호출은 입력 인자로 전달받은 명령을 수행하고 끝날 때까지 대기합니다.

void ehglobal::clrscr()
{
    system("cls");
}

기능 키를 입력받는 메서드를 구현합시다.

keydata ehglobal::getkey()
{
getchar 함수는 엔터를 치기 전까지 반응하지 않지만 getch함수와 getche 함수는 누르면 바로 반응합니다. getche는 누를 키를 화면에 표시하고 getch는 표시하지 않습니다. 여기에서는 메뉴를 선택하기 위한 목적이므로 콘솔 화면에 표시하지 않는 getch를 사용할게요.
    int key = getch();
ESC 키를 누르면 getch 함수는 27을 반환합니다.
    if(key == 27)//key가 27이면 ESC를 누른 것입니다.
    {
        return ESC;             
    }
만약 기능 키를 누르면 0을 반환합니다. 
    if(key == 0) //key가 0이면 기능 키를 누른 것입니다.
    {
그리고 다시 한 번 getch 함수를 호출해서 어느 기능 키를 누른 것인지 판별합니다.
        key = getch();//다시 한 번 getkey를 호출해야 합니다.
다음은 누른 키에 따라 적절한 기능 키를 반환하게 구현한 것입니다.
        switch(key)//key의 값에 따라 기능 키를 정의한 열거형의 상수를 반환합니다.
        {
        case 59: return F1;     case 60: return F2;
        case 61: return F3;     case 62: return F4;
        case 63: return F5;     case 64: return F6;
        case 65: return F7;     case 66: return F8; 
        }
             }
    return NO_DEFINED;
}

아무 키나 누를 때까지 대기하는 함수는 단순히 getkey를 호출합니다.

void ehglobal::waitkey()
{
    getkey();
}

이제 EhNara에서 Run 메서드를 구현합시다. Run 메서드에서는 메뉴를 선택하면 선택한 기능을 수행하는 것을 반복하면 되겠죠.

반복 선택한 메뉴가 종료 키가 아니면

선택한 키에 따라

        F1 일 때 학생 생성

        F2 일 때 초점 이동

        F3 일 때 학생 이동

        F4 일 때 전체 보기

void EhNara::Run()
{
    int key = 0;
    while((key = SelectMenu())!=ESC)
    {
        switch(key)
        {
            case F1: MakeStudent(); break;
            case F2: MoveFocus();  break;
            case F3: MoveStudent(); break;
            case F4: ViewAll(); break;
            default: cout<<"잘못 선택하였습니다."<<endl; break;
        }
        cout<<"아무 키나 누르세요."<<endl;
        ehglobal::getkey();
    }
}

메뉴 출력 및 선택에서는 먼저 메뉴를 출력한 후에 ehglobal 클래스의 정적 메서드 getkey를 호출하여 입력한 키를 반환합니다.

int EhNara::SelectMenu() //메뉴 출력 및 선택
{
    cout<<"이에이치 나라 메뉴"<<endl;
    cout<<"F1: 학생 생성 F2: 초점 이동 F3: 학생 이동 F4: 전체 보기 ESC: 프로그램 종료"<<endl;
    return ehglobal::getkey();
}

각 기능에서는 어떠한 기능을 선택했는지 확인할 수 있게 출력문을 작성하세요.

void EhNara::MakeStudent() //학생 생성
{
    cout<<"학생 생성"<<endl;
}
void EhNara::MoveFocus() //초점 이동
{
    cout<<"초점 이동"<<endl;
}
void EhNara::MoveStudent() //학생 이동
{
    cout<<"학생 이동"<<endl;
}
void EhNara::ViewAll() //전체 보기
{
    cout<<"전체 보기"<<endl;
}

이 중에서 초점 이동은 여기에서 구현합시다. 초점 이동에 따라 각 장소의 기능을 수행하는 부분도 유즈케이스 다이어그램에 정의하였으니 여기에서 작성합시다.

void EhNara::MoveFocus() //초점 이동
{
장소를 선택합니다.
    Place *place = SelectPlace();//장소 선택
잘못 선택하면 출력하고 기능을 끝냅니다.
    if(place==0)
    {
        cout<<"잘못 선택하였습니다."<<endl;
        return;
    }
잘 선택하였을 때는 장소의 상호 작용 메서드를 호출합니다.
    place->Run();//장소의 상호 작용
}

장소 선택하는 메서드를 구현합시다.

Place *EhNara::SelectPlace()//장소 선택
{
먼저 장소와 선택 방법을 출력합니다.
    ehglobal::clrscr();
    cout<<"장소를 선택하세요."<<endl;
    cout<<"1: 학교 2: 다운타운 3: 주거지"<<endl;
사용자로부터 장소를 입력받고 선택한 장소가 유효한지 확인합니다.
    int pi=0;
    cin>>pi;
    if((pi<1)&&(pi>PI_MAX))//유효한 선택이 아닐 때
    {
유효하지 않으면 0을 반환합니다.
        return 0;
    }
유효할 때 선택한 장소를 반환합니다.
    return places[pi-1];
}

장소의 Run 메서드도 EhNara의 Run 메서드와 비슷합니다.

void Place::Run()
{
    int key = 0;
    while((key = SelectMenu())!=ESC)
    {
선택한 기능에 따라 수행하는 부분이 장소에 따라 다릅니다. 대신 학생 복귀는 같습니다. 이에 F1일 때는 학생 복귀 기능을 호출하게 합시다.
        if(key == F1)
        {
            ComeBackStu();//학생 복귀
        }
        else
        {
그렇지 않으면 선택한 키에 따라 기능을 수행할 수 있게 합시다. 이를 위해 Place 클래스에서는 DoItByKey 메서드를 순수 가상 메서드로 선언하세요.
            DoItByKey(key);//키에 따라 기능 수행
        }

        cout<<"아무 키나 누르세요."<<endl;
        ehglobal::getkey();
    }
}

메뉴를 출력하고 선택하는 기능에서는 메뉴를 출력하는 부분을 별도의 메서드로 만들어서 호출하세요. 장소에 따라 기능이 다르기 때문에 ViewMenu 메서드는 가상 메서드로 정의하세요.

int Place::SelectMenu()
{
    ehglobal::clrscr();
    ViewMenu();
    return ehglobal::getkey();
}

장소의 ViewMenu 부분은 공통적인 부분만 구현합니다.

void Place::ViewMenu()const
{
    cout<<"ESC:초점 종료"<<endl;
    cout<<"F1: 이에이치 나라로 학생 "<<endl;
}

프로토 타이핑이므로 ComeBackStu 메서드는 어떤 기능인지 출력만 하세요.

void Place::ComeBackStu()//학생 복귀
{
    cout<<"학생 복귀"<<endl;
}

각 장소에서는 ViewMenu와 DoItByKey를 재정의하세요.

void Downtown::ViewMenu()const  //메뉴 출력
{
먼저 공통적인 메뉴를 출력하기 위해 Place의 ViewMenu 메서드를 호출한 후에 다른 기능을 출력하세요.
    Place::ViewMenu();
    cout<<"F2: 파티"<<endl;
    cout<<"F3: 노래방으로 가기"<<endl;
}
void Downtown::DoItByKey(int key)//키에 따라 기능 수행
{
기능에 따라 수행하는 부분을 작성하세요.
    switch(key)
    {
    case F2: Party(); break; //파티
    case F3: GoToSing(); break; //노래방으로 가기
    default: cout<<"잘못 선택하였습니다."<<endl;
    }
}

각 기능에서는 무엇을 선택했는지 파악할 수 있게 출력문을 작성하세요.

void Downtown::Party()//파티
{
    cout<<"파티"<<endl;
}
void Downtown::GoToSing()//노래방으로 가기
{
    cout<<"노래방으로 가기"<<endl;
}

학교와 주거지도 같은 방법으로 작성합니다.

다음은 현재까지 작성한 코드입니다.

//ehglobal.h
#pragma once
enum keydata //기능 키에 사용할 상수 열거
{
    NO_DEFINED,F1,F2,F3,F4,F5,F6,F7,F8, ESC
}; 

class ehglobal
{
public:
    static void clrscr();         //콘솔 화면 클리어
    static keydata getkey();   //기능 키 입력
    static void waitkey(); //아무 키나 누를 때까지 대기
private:
    ehglobal(void)
    {
    }
    ~ehglobal(void)
    {
    }      
};
//ehglobal.cpp
#include "ehglobal.h"           
#include <conio.h>
#include <windows.h> 

void ehglobal::clrscr()
{
    system("cls");    
}

keydata ehglobal::getkey()
{
    int key = getch();
    if(key == 27)//key가 27이면 ESC를 누른 것입니다.
    {
        return ESC;             
    }
    if(key == 0) //key가 0이면 기능 키를 누른 것입니다.
    {
        key = getch();//다시 한 번 getkey를 호출해야 합니다.
        switch(key)//key의 값에 따라 기능 키를 정의한 열거형의 상수를 반환합니다.
        {
        case 59: return F1;     case 60: return F2;
        case 61: return F3;     case 62: return F4;
        case 63: return F5;     case 64: return F6;
        case 65: return F7;     case 66: return F8;
        }
    }
    return NO_DEFINED;
}
void ehglobal::waitkey()
{
    getkey();
}
//Place.h
#pragma once
#include "ehglobal.h"
#include <iostream>
#include <string>
using namespace std;

class Place
{
public:
    Place(void);
    ~Place(void);
    void Run();
protected:
    int SelectMenu();
    virtual void ViewMenu()const;
    void ComeBackStu();//학생 복귀
    virtual void DoItByKey(int key)=0;//키에 따라 기능 수행
};
//Place.cpp
#include "Place.h"
Place::Place(void)
{
}
Place::~Place(void)
{
}
void Place::Run()
{
    int key = 0;
    while((key = SelectMenu())!=ESC)
    {
        if(key == F1)
        {
            ComeBackStu();//학생 복귀
        }
        else
        {
            DoItByKey(key);//키에 따라 기능 수행
        }
        cout<<"아무 키나 누르세요."<<endl;
        ehglobal::getkey();
    }
}

int Place::SelectMenu()
{
    ehglobal::clrscr();
    ViewMenu();
    return ehglobal::getkey();
}

void Place::ViewMenu()const
{
    cout<<"ESC:초점 종료"<<endl;
    cout<<"F1: 이에이치 나라로 학생 "<<endl;
}

void Place::ComeBackStu()//학생 복귀
{
    cout<<"학생 복귀"<<endl;
}
//School.h
#pragma once
#include "Place.h"
class School :
    public Place
{
public:
    School(void);
    ~School(void);
private:
    virtual void ViewMenu()const;  //메뉴 출력
    virtual void DoItByKey(int key);//키에 따라 기능 수행        
    void StartLecture();//강의 시작
    void GoToLibrary();//도서관으로 가기
};
//School.cpp
#include "School.h"
School::School(void)
{
}
School::~School(void)
{
}
void School::ViewMenu()const  //메뉴 출력
{
    Place::ViewMenu();
    cout<<"F2: 강의 시작"<<endl;
    cout<<"F3: 도서관으로 가기"<<endl;
}
void School::DoItByKey(int key)//키에 따라 기능 수행
{
    switch(key)
    {
    case F2: StartLecture(); break; //강의 시작
    case F3: GoToLibrary(); break; //도서관으로 가기
    default: cout<<"잘못 선택하였습니다."<<endl;
    }
}
void School::StartLecture()//강의 시작
{
    cout<<"강의 시작"<<endl;
}
void School::GoToLibrary()//도서관으로 가기
{
    cout<<"도서관으로 가기"<<endl;
}

//Village.h
#pragma once
#include "Place.h"
class Village :
    public Place
{
public:
    Village(void);
    ~Village(void);
private:
    virtual void ViewMenu()const;  //메뉴 출력
    virtual void DoItByKey(int key);//키에 따라 기능 수행
    void TurnOff();//소등
    void GoToLivingRoom();//거실로 가기
};
//Village.cpp
#include "Village.h"
Village::Village(void){    }
Village::~Village(void){    }
void Village::ViewMenu()const  //메뉴 출력
{
    Place::ViewMenu();
    cout<<"F2: 소등"<<endl;
    cout<<"F3: 거실로 가기"<<endl;
}
void Village::DoItByKey(int key)//키에 따라 기능 수행
{
    switch(key)
    {
    case F2: TurnOff(); break; //강의 시작
    case F3: GoToLivingRoom(); break; //도서관으로 가기
    default: cout<<"잘못 선택하였습니다."<<endl;
    }
}
void Village::TurnOff()//소등
{
    cout<<"소등"<<endl;
}
void Village::GoToLivingRoom()//거실로 가기
{
    cout<<"거실로 가기"<<endl;
}
//Downtown.h
#pragma once
#include "Place.h"
class Downtown:
        public Place
{
public:
    Downtown(void);
    ~Downtown(void);
private:
    virtual void ViewMenu()const;  //메뉴 출력
    virtual void DoItByKey(int key);//키에 따라 기능 수행
    void Party();//파티
    void GoToSing();//노래방으로 가기
};
//Downtown.cpp
#include "Downtown.h"
Downtown::Downtown(void){    }
Downtown::~Downtown(void){    }
void Downtown::ViewMenu()const  //메뉴 출력
{
    Place::ViewMenu();
    cout<<"F2: 파티"<<endl;
    cout<<"F3: 노래방으로 가기"<<endl;
}
void Downtown::DoItByKey(int key)//키에 따라 기능 수행
{
    switch(key)
    {
    case F2: Party(); break; //파티
    case F3: GoToSing(); break; //노래방으로 가기
    default: cout<<"잘못 선택하였습니다."<<endl;
    }
}
void Downtown::Party()//파티
{
    cout<<"파티"<<endl;
}
void Downtown::GoToSing()//노래방으로 가기
{
    cout<<"노래방으로 가기"<<endl;
}
//EhNara.h
#pragma once
#include "Place.h"
class EhNara
{
    static EhNara app;//단일체
    enum PIndex//장소 인덱스
    {
        PI_SCHOOL, PI_DOWNTOWN, PI_VILLAGE, PI_MAX
    };
    Place *places[PI_MAX];    
public:
    static void Start();//응용 시작 - 진입점에서 호출하는 정적 메서드
private:
    EhNara(void);
    ~EhNara(void);
    void Initialize(); //초기화
    void Run(); //사용자와 상호 작용
    void Exit(); //해제화
    int SelectMenu(); //메뉴 출력 및 선택
    void MakeStudent(); //학생 생성
    void MoveFocus(); //초점 이동
    void MoveStudent(); //학생 이동
    void ViewAll(); //전체 보기

    Place *SelectPlace();//장소 선택
};
//EhNara.cpp
#include "EhNara.h"
#include "School.h"
#include "Downtown.h"
#include "Village.h"
EhNara EhNara::app;//단일체
void EhNara::Start()//응용 시작 - 진입점에서 호출하는 정적 메서드
{
    app.Initialize();
    app.Run();
    app.Exit();
}
EhNara::EhNara(void)
{
}
EhNara::~EhNara(void)
{
}
void EhNara::Initialize()
{
    //장소 개체 생성
    places[PI_SCHOOL] = new School();
    places[PI_DOWNTOWN] = new Downtown();
    places[PI_VILLAGE] = new Village();
}

void EhNara::Run()
{
    int key = 0;
    while((key = SelectMenu())!=ESC)
    {
        switch(key)
        {
            case F1: MakeStudent(); break;
            case F2: MoveFocus();  break;
            case F3: MoveStudent(); break;
            case F4: ViewAll(); break;
            default: cout<<"잘못 선택하였습니다."<<endl; break;
        }
        cout<<"아무 키나 누르세요."<<endl;
        ehglobal::getkey();
    }
}
void EhNara::Exit() //해제화
{
    for(int i = 0; i<PI_MAX; i++)//모든 장소 개체 해제
    {
        delete places[i];
    }
}

int EhNara::SelectMenu() //메뉴 출력 및 선택
{
    ehglobal::clrscr();
    cout<<"이에이치 나라 메뉴"<<endl;
    cout<<"F1: 학생 생성 F2: 초점 이동 F3: 학생 이동 F4: 전체 보기 ESC: 프로그램 종료"<<endl;
    return ehglobal::getkey();
}
void EhNara::MakeStudent() //학생 생성
{
    cout<<"학생 생성"<<endl;
}
void EhNara::MoveFocus() //초점 이동
{
    Place *place = SelectPlace();//장소 선택
    if(place==0)
    {
        cout<<"잘못 선택하였습니다."<<endl;
        return;
    }
    place->Run();//장소의 상호 작용
}
void EhNara::MoveStudent() //학생 이동
{
    cout<<"학생 이동"<<endl;
}
void EhNara::ViewAll() //전체 보기
{
    cout<<"전체 보기"<<endl;
}
Place *EhNara::SelectPlace()//장소 선택
{
    cout<<"장소를 선택하세요."<<endl;
    cout<<"1: 학교 2: 다운타운 3: 주거지"<<endl;
    int pi=0;
    cin>>pi;
    if((pi<1)&&(pi>PI_MAX))//유효한 선택이 아닐 때
    {
        return 0;
    }
    return places[pi-1];
}
//Program.cpp
#include "EhNara.h"
int main()
{
    EhNara::Start();
    return 0;
}

여러분께서 현재까지 제대로 동작하는지 컴파일하고 테스트해 보세요.