[C#] 7.3 설계한 내용 프로젝트에 옮기기

이제는 앞에서 설계한 내용을 기반으로 프로젝트에 필요한 클래스를 추가하고 필요한 멤버를 추가해 보기로 합시다. 먼저, 설계 단계의 클래스 다이어그램에 해당하는 클래스를 프로젝트에 추가하시고 일반화 관계에 있으면 상속에 대해 표현하시기 바랍니다.

프로젝트에 클래스 추가 및 상속 표현
[그림 41] 프로젝트에 클래스 추가 및 상속 표현

캠퍼스 생활(CampusLife)은 단일체로 정의하기로 하였으니 이에 대해 표현을 합시다. 단일체를 표현하는 방법은 캡슐화의 생성자 항목에서 설명했으므로 별도의 설명은 하지 않겠습니다.

▶ CampusLife.cs

namespace EX_CampusLife
{
    class CampusLife 
    {
        static CampusLife singleton = new CampusLife(); //단일체 생성
        internal static CampusLife Singleton //단일체에 대한 정적 속성
        {
            get{     return singleton;    }
        }
        private CampusLife() //단일체로 구현하기 위해 private으로 접근 지정
        {
        }
    }
}

프로젝트에 필요한 클래스를 추가하고 관계에 맞게 상속 및 단일체를 표현하였으면 이제 시퀀스 다이어그램을 보며 필요한 멤버를 추가하기로 합시다. 시퀀스 다이어그램에서 호출로 도식된 것은 호출 개체에서 피 호출 개체에 있는 멤버를 사용하는 것이기 때문에 피 호출 개체에 멤버를 추가하고 접근 지정을 internal로 지정하면 되겠죠. 이 프로그램 실습에서 시퀀스 다이어그램에서는 서로 다른 개체 사이에 호출만 표현하였기 때문에 앞으로 시퀀스 다이어그램에 약속한 호출에 해당하는 멤버는 모두 접근 지정을 internal로 하세요.

먼저, 초기화 단계에서 캠퍼스와 장소들을 생성하는 시퀀스 다이어그램을 보시면 기본 생성자를 통해 개체를 생성하고 있으니 이들을 추가하기로 합시다. 다음은 Campus 클래스에 기본 생성자를 추가한 예제 코드입니다. 시퀀스 다이어그램에는 장소들을 생성하는 부분도 있으니 각 장소에도 같은 원리로 기본 생성자를 추가해야겠지요.

Class Campus
{
    internal Campus() //기본 생성자
    {
        throw new NotImplementedException();
    }
}

초기화 단계에서 학생 생성하는 시퀀스 다이어그램을 보시면 학생 개체를 생성할 때 입력 인자로 학번과 이름을 입력 인자로 전달하고 있습니다. 이에 Student 클래스와 이를 기반으로 파생하는 클래스에 두 개의 입력 매개 변수를 갖는 생성자를 추가합시다. 그리고 생성한 개체를 캠퍼스 개체에 보내는 InStudent 호출은 Campus 클래스에 멤버 메서드로 추가하세요.

class Student
{
    internal Student(int snum, string name) //생성자
    {
        throw new NotImplementedException();
    }
}

다음은 CStudent 클래스에 생성자를 추가한 예제 코드입니다. CStudent는 Student를 기반으로 파생한 형식이고 기반 형식에 기본 생성자가 없으므로 base 키워드를 이용하여 생성자를 초기화해 주어야 할 것입니다. MStudent와 PStudent 클래스도 같은 방법으로 생성자를 추가해야겠지요.

class CStudent:Student
{
    internal CStudent(int snum, string name)
        : base(snum, name) //기반 형식 생성자 초기화
    {
        throw new NotImplementedException();
    }
}

Campus 클래스에는 InStudent 멤버 메서드를 추가하세요. 이제 Campus 클래스에는 기본 생성자와 InStudent 멤버 메서드를 캡슐화했네요.

class Campus
{
    internal Campus()
    {
        throw new NotImplementedException();
    }
    internal void InStudent(Student student) 
    {
        throw new NotImplementedException();
    }
}

이제 학생 이동 시퀀스 다이어그램에 약속한 호출들을 추가해 봅시다.

해당 시퀀스 다이어그램을 보면 캠퍼스 클래스에는 학생의 수를 반환하는 GetStuCount 와 GetStuInfo, GetStudent 메서드를 추가하면 되겠죠. 그런데, GetStuCount는 멤버 속성으로 하면 어떨까요? 그리고 GetStudent를 인덱서로 하면 어떨까요? 어떠한 것으로 하더라도 큰 문제가 되지 않지만, C# 언어에서 제공하는 문법적 요소의 특징을 고려한다면 변경하는 것이 나을 것 같습니다.

이처럼 개발 과정에서 이전 단계들에서 약속한 것을 수정하기를 원한다면 약속한 문서를 반드시 수정하는 것을 잊지 마시기 바랍니다. 실제 개발에서도 각 개발 공정을 완벽하게 마무리하고 다음 단계로 넘어가겠다는 것은 굉장히 위험하고 비용이 막대하게 들 수 있습니다. 될 수 있으면 각 개발 공정에서 해야 할 작업을 명확하고 정확하게 해 나가야겠지만 이후 공정에서 이전 공정에서 약속한 것보다 더 나은 것을 발견하게 되면 이에 관한 토론을 거쳐 수정할 것인지를 판단하는 것이 좋습니다. 그리고 수정하기로 했다면 반드시 문서에 반영하시기 바랍니다. 만약, 수정하기로 개발자 사이에 구두로 약속하고 문서에 반영하지 않고 작업을 진행하면 이후에 누가 약속에 맞게 한 것인지에 대한 논쟁때문에 충돌이 발생할 수 있습니다. 이에 약속을 수정했다면 문서도 이를 반영하는 것이 바람직합니다.

수정한 학생 이동 시퀀스 다이어그램
[그림 42] 수정한 학생 이동 시퀀스 다이어그램

수정한 시퀀스 다이어그램을 바탕으로 Campus 클래스에 세 개의 멤버를 추가합시다.

class Campus
{
    internal int StuCount //학생 수 가져오기 속성 제공 
    {
       get
       {
           throw new NotImplementedException();
        }
    }
    internal Student this[int snum] //인덱서
    {
        get
        {
            throw new NotImplementedException();
       }
    }
    internal Campus()
    {
        throw new NotImplementedException(); 
    }
    internal void InStudent(Student student)
    {
       throw new NotImplementedException();
    }
    internal string GetStuInfo(int nth)
    {
        throw new NotImplementedException();
    }
}

그리고 학생 이동 시퀀스 다이어그램을 보면 Place에는 ToString 메서드와 InStudent 메서드를 추가하세요. ToString은 object 클래스에 가상 메서드로 정의된 것이므로 재정의해야겠지요. Place에서 파생된 각 장소에도 ToString 메서드를 재정의하세요.

class Place
{
    internal Place()
    {
        throw new NotImplementedException();
    }
    internal void InStudent(Student student)
    {
        throw new NotImplementedException();
    }
    public override string ToString() //재정의
    {
        throw new NotImplementedException();
    }
}

Student에도 ToString 메서드를 재정의하고 Num 속성의 get 블록을 추가합시다.

class Student
{
    internal int Num //학생 번호 속성 가져오기 제공
    {
        get
        {
            throw new NotImplementedException();
        }
    }
    internal Student(int snum, string name)
    {
        throw new NotImplementedException();
    }
    public override string ToString() //재정의
    {
        throw new NotImplementedException();
    }
}

이번에는 초점 이동에 대한 시퀀스 다이어그램을 보면서 멤버를 추가해 봅시다. 초점 이동에 대한 시퀀스 다이어그램에서는 각 장소에 따라 다르게 수행되는 부분은 약속하지 않고 공통적인 부분만 약속하였습니다. 초점 이동에 관한 시퀀스를 보시면 각 장소로 초점이 이동되고 나서 다시 캠퍼스 생활로 초점이 돌아오면 어떻게 학생들을 복귀시킬 것인지에 대한 시퀀스가 약속되어 있습니다. 먼저, 캠퍼스 생활에서 해당 장소에 학생의 수를 얻어오는 부분이 있고 특정 학생의 정보를 얻어오는 부분이 있습니다. 그리고 사용자에 의해 선택된 학생 번호에 해당하는 학생을 얻어오는 메서드가 약속되어 있네요. 그리고 캠퍼스 생활에서 캠퍼스 개체에게 복귀할 학생을 보내는 것을 약속했었죠.

먼저, Place 클래스에 새로운 멤버를 추가해 봅시다. 해당 장소에 있는 학생 수를 반환하는 GetStuCount 메서드와 특정 학생의 정보를 문자열로 반환하는 GetStuInfo 메서드, 특정 번호의 학생을 반환하는 인덱서를 추가하면 되겠죠. 시퀀스 정의에서는 특정 번호의 학생을 반환하는 부분을 메서드로 약속되어 있는데 이 부분은 인덱서의 get 블록으로 추가할게요. 약속이 변경되었으니 시퀀스 다이어그램에도 이를 반영하시기 바랍니다.

class Place
{
    ....중략...
    internal Student this[int snum] //인덱서
    {
        get
        {
            throw new NotImplementedException();
        }
    }
    internal int GetStuCount()
    {
        throw new NotImplementedException();
    }

    internal string GetStuInfo(int nth)
    {
        throw new NotImplementedException();
    }
}

캠퍼스 개체에 학생을 보내는 메서드를 비롯하여 다른 멤버는 이미 추가했네요.

이번에는 초점이 강의실로 이동되어 판서 강의하는 부분을 시퀀스 다이어그램을 보며 필요한 멤버를 추가해 봅시다. 캠퍼스 생활에서는 강의실에 판서 강의하라는 것을 DoIt 메서드에 해당 행위를 인자로 전달하게 되어 있네요. 이 부분은 기능 확장을 쉽게 하기 위해 각 장소에 따라 수행할 수 있는 일의 종류를 정의하고 입력 인자로 해당 일의 종류를 전달받아 수행하게 한 것입니다. 따라서 다른 시퀀스 다이어그램까지 살펴보면 DoIt 메서드는 각 장소의 기반 클래스인 Place에 추상 메서드로 약속하고 각 장소에서는 재정의하면 된다는 것을 생각할 수 있을 것입니다.

class Place
{
    ....중략...
    internal abstract void DoIt(int cmd);
}
class LectureRoom:Place
{
    internal LectureRoom()
    {
        throw new NotImplementedException();
    }
    internal override void DoIt(int cmd)
    {
        throw new NotImplementedException();
    }
}

그리고 강의실에서는 모든 학생에게 강의를 듣게 하고 도전적인 학생일 경우 질문을 하게 되어 있네요. 이에 학생 클래스에는 ListenLecture 멤버 메서드를 추가하고 CStudent에는 Question 메서드를 추가합시다.

class Student
{
    ... 중략 ...
    internal void ListenLecture()
    {
       throw new NotImplementedException();
    }
}
class CStudent:Student
{
    ... 중략 ...
    internal void Question()
    {
        throw new NotImplementedException();
    }
}

이번에는 발표 수업에 관한 시퀀스 다이어그램을 보면 필요한 멤버를 추가해 봅시다. 발표 수업에서 학생들 정보를 출력하는 부분은 이미 추가하였기 때문에 별다른 작업을 할 필요가 없습니다. 발표할 학생을 선택하였을 때 해당 학생에게 발표를 수행하게 하려면 캠퍼스 생활에서 강의실 개체에 해당 행위와 선택한 학생 정보를 인자로 전달하는 메서드 DoIt을 추가해야겠지요. 이 메서드 또한 다른 시퀀스 다이어그램들을 보면 각 장소의 기반 클래스에서 추상 메서드로 제공하고 각 장소에서 특정 학생에게 수행할 작업을 약속하여 이를 수행하는 부분을 재정의하세요.

class Place
{
    ... 중략 ...
    internal abstract void DoIt(int cmd,int snum);
}
class LectureRoom:Place
{
    ... 중략 ...
    internal override void DoIt(int cmd, int snum)
    {
        throw new NotImplementedException();
    }
}

그리고 강의실에서 특정 학생에게 발표를 시키는 것과 토론을 시키는 부분이 있으니 각 메서드를 Student 클래스에 멤버 메서드로 추가합시다.

class Student
{
    ... 중략 ...
    internal void Announce()
    {
        throw new NotImplementedException();
    }
    internal void Discuss()
    {
       throw new NotImplementedException();
    }
}

이번에는 초점이 도서관에 온 상태에서 세미나 메뉴를 선택했을 때에 대해 살펴봅시다. 해당 시퀀스에서는 캠퍼스 생활에서 도서관 개체에 해당 행위를 수행하라는 DoIt 메서드를 호출하면 도서관에서 학생들에게 세미나를 듣게 하기로 시퀀스를 약속하였습니다. 이미 각 장소의 기반 클래스인 Place에는 행위를 인자로 전달받는 DoIt 메서드를 추상 메서드로 약속하였으니 Library에서는 이를 재정의하면 될 것입니다. 그리고 Student 클래스에는 세미나를 듣는 ListenSeminar 메서드를 추가하세요.

class Library:Place
{
    ... 중략 ...
    internal override void DoIt(int cmd)
    {
        throw new NotImplementedException();
    }
}
class Student
{
    ... 중략 ...
    internal void ListenSeminar()
    {
        throw new NotImplementedException();
    }
}

이번에는 초점이 도서관에 온 상태에서 책 읽기 메뉴를 선택했을 때 약속한 멤버를 추가해 봅시다. 해당 시퀀스를 보면 특정 학생을 선택하기 위한 부분과 선택된 학생에게 책을 읽게 하는 부분이 있는데 선택하는 부분에 필요한 멤버는 이미 앞에서 추가하였습니다. 그리고 선택된 학생에게 특정 행위를 수행하게 하는 DoIt 메서드는 장소들의 기반 클래스인 Place에서 추상 메서드로 추가된 상태이므로 이에 대하여 Library 클래스에서 재정의하면 되겠죠. 그리고 Student 클래스에 책을 읽게 하는 Reading 메서드를 추가합시다.

class Library:Place
{
    .... 중략 ...
    internal override void DoIt(int cmd, int snum)
    {
        throw new NotImplementedException();
    }
}
class Student
{
    ... 중략 ...
    internal void Reading()
    {
        throw new NotImplementedException();
    }
}

이번에는 기숙사에 초점이 온 상태에서 잠자기 메뉴를 선택했을 때에 대하여 시퀀스를 보며 필요한 멤버를 추가해 봅시다. 캠퍼스 생활에서 기숙사 개체에 잠자기 메뉴를 선택하였다는 것은 DoIt 메서드에 인자로 잠자기를 선택하였음을 전달하게 약속되어 있으니 Place에 추상 메서드로 캡슐화되어 있는 것을 기숙사에서 재정의하면 될 것입니다. 그리고 학생에게 잠을 자게 해야 하므로 Student 클래스에 Sleep 메서드를 추가하고 수동적인 학생에게는 잠꼬대하게 해야 하므로 PStudent 클래스에 TalkingInSleep 메서드를 추가합시다.

class Dormitory:Place
{
    ... 중략 ...
    internal override void DoIt(int cmd)
    {
        throw new NotImplementedException();
    }
}
class Student
{
    ... 중략 ...
    internal void Sleep()
    {
        throw new NotImplementedException();
    }
}
class PStudent:Student
{
    ... 중략 ...
    internal void TalkingInSleep()
    {
        throw new NotImplementedException();
    }
}

초점이 기숙사에 온 상태에서 TV 시청을 선택했을 때에 대하여 시퀀스 다이어그램을 보며 필요한 부분을 추가해 봅시다. 여기에서는 기숙사에서 학생에게 TV를 시청하게 하는 WatchingTV 메서드를 추가하세요.

class Student
{
   ... 중략 ...
    internal void WatchingTV()
    {
        throw new NotImplementedException();
    }
}

마지막으로 전체 보기에 대하여 시퀀스 다이어그램에 약속된 멤버를 추가해 봅시다. 시퀀스 다이어그램을 보면 캠퍼스 개체에 학생의 수를 얻어오고 특정 학생의 정보를 문자열로 얻어오는 등의 작업을 수행하게 되어 있는데 새롭게 추가해야 할 것이 없네요.