티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.

 

이번 Chapter에서는 객체지향에서 가장 중요하다고 할 수 있는 '다형성'을 공부합니다. 그리고 앞서 제시한 '오렌지 미디어 급여 관리 확장성 문제'를 해결하게 됩니다.

 

객체 포인터와 참조 관계

이전에 제시한 예제들을 통해서도 보았듯이, 클래스를 기반으로도 포인터 변수를 선언할 수 있습니다. 예를 들어 Person이라는 이름의 클래스가 정의되었다면, Person 객체의 주소 값 저장을 위해서 다음과 같이 포인터 변수를 선언할 수 있습니다.

Person* ptr;
ptr = new Person;

 

그런데 Person형 포인터는 Person 객체뿐만 아니라, Person을 상속하는 유도 클래스의 객체도 가리킬 수 있습니다. 예를 들어 Person을 상속하는 Student 클래스가 다음과 같이 정의되어 있을 때,

class Student : public Person
{
	......
};

 

Person형 포인터 변수는 다음의 코드에서 보이듯이 Student 객체도 가리킬 수 있습니다.

Person* ptr;
ptr = new Student;

 

여기서 더 나아가 Student 클래스를 상속하는 유도 클래스 PartTimeStudent가 다음과 같이 정의되어 있다면,

class PartTimeStudent : public Student
{
	......
};

 

Person형 포인터 변수는 다음의 코드에서 보이듯이 PartTimeStudent 객체도 가리킬 수 있습니다.

Person* ptr;
ptr = new PartTimeStudent;

 

위 예에서 Person과 PartTimeStudent의 관계를 간접 상속 관계라고 합니다. PartTimeStudent가 Person을 직접 상속하고 있지는 않지만, Person을 상속한 Student를 상속하고 있기 때문입니다. 간접 상속이라는 용어를 사용해 위 예를 설명하자면 다음과 같습니다.

  • 객체 포인터 변수는 해당 객체의 클래스를 직접 혹은 간접 상속하고 있는 모든 객체를 가리킬 수 있습니다.

 

앞서, '오렌지 미디어 급여관리 확장성 문제'를 접했었습니다. 해당 문제는 고용 형태가 정규직뿐만 아니라, 영업직, 임시직이 추가되면서 프로그램을 거의 새로 만들다시피 새로 작성해야 하는 것에서 비롯되었습니다. 

 

정규직, 영업직, 임시직은 피고용인의 한 형태, 즉, 피고용인의 일종이며, 영업직은 정규직의 일종입니다.

 

따라서 정규직과 임시직은 피고용인을 상속받을 수 있고, 영업직은 정규직을 상속받을 수 있습니다. 그렇게 되면 정규직과 영업직, 임시직은 모두 피고용인을 직접 혹은 간접 상속받게 됩니다. 이때 피고용인을 가리키는 포인터를 이용해서 정규직, 영업직, 임시직 모두를 가리킬 수 있게 됩니다. 눈치를 챘는지 모르겠지만 객체 포인터의 이러한 특성이 '오렌지 미디어 급여관리 확장성 문제'의 해결책의 1차 실마리가 될 수 있습니다.

 

오렌지 미디어 급여관리 프로그램에 EmployeeHandler 클래스를 만들고, 이 클래스가 관리하는 대상이 Employee객체가 되게 하면 정규직 외에 영업직과 임시직이 추가되어도 EmployeeHandler 클래스를 변경할 필요는 없게 됩니다.

 

다음 예제를 살펴보겠습니다.

#include <iostream>
#include <cstring>
using namespace std;

class Employee
{
private:
	char name[40];
public:
	Employee(const char* name)
	{
		strcpy(this->name, name);
	}

	void ShowYourName() const
	{
		cout << "name : " << name << endl;
	}
};

class PermanentWorker : public Employee
{
private:
	int salary;
public:
	PermanentWorker(const char* name, int money)
		:Employee(name), salary(money) {}

	int GetPay() const
	{
		return salary;
	}

	void ShowSalaryInfo() const
	{
		ShowYourName();
		cout << "salary : " << salary << endl << endl;
	}
};

class EmployeeHandler
{
private:
	Employee* empList[10];
	int empNum;
public:
	EmployeeHandler() : empNum(0) {}
	void AddEmployee(Employee* emp)
	{
		empList[empNum++] = emp;
	}

	void ShowAllSalaryInfo() const
	{
		/*
		for (int i = 0l i < empNum; i++)
		{
			empList[i]->ShowSalaryInfo();
		}
		*/
	}

	void ShowTotalSalary() const
	{
		int sum = 0;
		/*
		for (int i = 0; i < empNum; i++)
		{
			sum += empList[i]->GetPay();
		}
		*/

		cout << "salary sum: " << sum << endl;
	}

	~EmployeeHandler()
	{
		for (int i = 0; i < empNum; i++)
		{
			delete empList[i];
		}
	}
};

int main(void)
{
	//직원관리를 목적으로 설계된 컨트롤 클래스의 객체생성
	EmployeeHandler handler;

	//직원 등록
	handler.AddEmployee(new PermanentWorker("KIM", 1000));
	handler.AddEmployee(new PermanentWorker("LEE", 1500));
	handler.AddEmployee(new PermanentWorker("KOEY", 4000));

	//이번 달에 지불해야 할 급여의 정보
	handler.ShowAllSalaryInfo();

	//이번 달에 지불해야할 급여의 총합
	handler.ShowTotalSalary();

	return 0;
}

/*
실행결과

salary sum: 0

*/

 

위 예제를 보면 PermanentWorker 클래스는 Employee 클래스를 상속받고 있습니다. 그리고 EmployeeHandler 클래스는 Employee 객체를 가리키는 포인터 배열을 멤버로 가지고 있습니다. 이후 main 함수를 보면, PermanentWorker 클래스가 Employee 클래스를 상속받고 있기 때문에 Employee 객체를 가리키는 포인터에 PermanentWorker 객체의 주소를 저장할 수 있었습니다.

 

Employee 포인터 변수가 PermanentWorker 객체를 가리키게 해도 이 포인터를 이용해서 PermanentWorker의 멤버에 접근할 수는 없습니다. 위 예제에서 주석 처리된 부분이 그런 시도를 한 부분에 해당되며, 해당 코드는 컴파일 에러를 발생시키기 때문에 주석처리를 하였습니다.

 

Employee 포인터가 Employee를 상속한 PermanentWorker 객체를 가리킬 수 있는 것은 단순히 PermanentWorker객체가 Employee의 멤버를 모두 가지고 있기 때문입니다. 그래서 Employee 포인터는 PermanentWorker 객체를 가리키면서도 이것이 Employee 객체라고 생각하게 됩니다. 그런데 이 포인터를 통해서 PermanentWorker 객체만이 가지고 있는 멤버에 접근하려고 하면 당연히 에러가 발생합니다. 왜냐하면 Employee 객체에는 없는 멤버이기 때문입니다. 

 

함수의 오버 라이딩

 

이제 이어서 영업직과 임시직에 대해서도 추가로 정의해보겠습니다. 먼저 임시직에 해당하는 클래스입니다.

class TemporaryWorker : public Employee
{
private:
	int workTime;      //일한 시간
	int payPerHour;    //시급
public:
	TemporaryWorker(char* name, int pay)
		: Employee(name), workTime(0), payPerHour(pay) {}

	void AddWorkTime(int time)
	{
		workTime += time;
	}

	int GetPay() const
	{
		return workTime * payPerHour;
	}

	void ShowSalaryInfo() const
	{
		ShowYourName();
		cout << "salary : " << GetPay() << endl << endl;
	}
};

 

다음은 영업직에 해당하는 클래스입니다.

class SalesWorker : public PermanentWorker
{
private:
	int salesResult;      //월 판매실적
	double bonusRatio;    //상여금 비율
public:
	SalesWorker(char* name, int money, double ratio)
		: PermanentWorker(name, money), salesResult(0), bonusRatio(ratio) {}

	void AddSalesResult(int value)
	{
		salesResult += value;
	}

	int GetPay() const
	{
		return PermanentWorker::GetPay() + (int)(salesResult * bonusRatio);
	}

	void ShowSalaryInfo() const
	{
		ShowYourName();
		cout << "salary : " << GetPay() << endl << endl;
	}
};

 

영업직 클래스를 살펴보면 조금 특이한 부분이 있습니다. 바로 GetPay 함수입니다. 영업직 클래스인 SalesWorker 클래스는 PermanentWorker 클래스를 상속받고 있습니다. 그런데 PermanentWorker 클래스에도 같은 이름의 GetPay 함수가 있습니다. 이러한 형태를 '함수의 오버 라이딩'이라고 합니다. 함수의 오버 로딩과는 다릅니다.

 

함수가 오버 라이딩되면, 오버 라이딩된 기초 클래스의 함수는 유도 클래스의 함수에 가려집니다. 그래서 유도 클래스에서 기초 클래스의 함수를 호출하려면 기초 클래스의 이름에 ::연산자를 사용하여 다음과 같이 접근해야 합니다. 

SalesWorker seller("Hong", 1000, 0.1);
cout << seller.PermanentWorker::GetPay() << endl;

 

그리고 위에 정의된 SalesWorker 클래스와 PermanentWorker 클래스를 보면 ShowSalaryInfo 함수가 오버 라이딩된 것을 확인할 수 있습니다. 두 클래스의 ShowSalaryInfo 함수는 형태가 완벽히 같습니다. 그러면 PermanentWorker 클래스를 상속받고 있는 SalesWorker 클래스는 PermanenetWorker 클래스의 ShowSalaryInfo 함수를 사용해도 됐을 것입니다. 하지만 그렇지 않습니다.

 

ShowSalaryInfo 함수 안에는 GetPay 함수를 호출하고 있습니다. GetPay 함수 또한 SalesWorker 클래스와 PermanentWorker 클래스에서 오버 라이딩되어 있습니다. 따라서 만약 SalesWorker 객체에서 PermanentWorker 클래스의 ShowSalaryInfo 함수를 호출하게 되면, SalesWorker 클래스의 GetPay함수를 호출하기를 기대하지만, PermanentWorker 클래스의 것으로 호출됩니다. 그렇기 때문에 ShowSalaryInfo 함수도 오버 라이딩되었습니다.

 

ShowSalaryInfo 함수가 오버 라이딩된 이유는 오로지 내부에서 호출되는 GetPay 함수가 오버 라이딩되었기 때문이라고 볼 수 있습니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함