객체지향프로그래밍(11) - 상속(1)

Class Diagram을 통해 상속 이해하기

Featured image

🔚 짧게 하는 복습

✅ 1. OOD(Object Oriented Design)을 통해서 OOP(Object Oriented Programming)를 구현할 수 있다.

✅ 2. 객체지향 프로그래밍의 장점을 이해한다.

혹시 기억이 안 난다면, 다시 돌아가자


우리는 지금까지 객체지향 프로그래밍을 도입하게 된 이유, 실질적으로 어떻게 구현하는지에 대해서 배웠다.

특히 저번 시간에는 포켓몬 게임을 객체지향 스타일로 코드를 작성하며, 나아진 점과 여전히 개선이 필요한 점을 다루었다.

저번 시간 코드의 개선해야 할 점 중 첫 번째, 거의 같은 클래스인데 약간 달라서 중복 선언이 필요했던 상황을 개선해보자.


Class Diagram이란?

우리가 앞으로는 클래스 하나를 다루기보다는 여러 클래스 간의 관계를 다룰 것이다.

그런 의미에서 클래스를 쉽게 표현하는 방법을 배울 것이다.

우리가 만든 클래스가 지금은 겨우 4개이지만, 실제로 포켓몬 게임을 기획한다고 해보자.

수십 개, 적어도 수백 개의 포켓몬이 필요하다.

이를 클래스 정의문으로 누군가가 출력해온다면, 정의만 읽다가 하루가 다 갈 것이다.

그뿐일까? 다 읽어도 머리에 기억이 남는 것도 하나도 없을 것이다.

이럴 때 가장 쉬운 방법은 간단하게 추상화하여 도식화하는 것이다.

Class Diagram이란 클래스의 변수와 메소드만 따로 간단하게 나타낸 것으로, 아래와 같다.

교재마다 조금은 다르지만, 변수와 메소드를 한눈에 표시한다는 점은 같다.

우리가 사용한 클래스를 Class Diagram으로 모두 정리해보자면, 아래와 같다.

코드로 적어놓은 것보다는 훨씬 쉽게 기능들을 파악할 수 있다.

Class Diagram으로 살펴보니 확실히 중복되는 속성이 잘 보인다.

포켓몬 간의 변수는 모두 같고, 생성자/소멸자/복사 생성자를 제외한 메소드도 사실 모두 같다.

이런 경우에는 상속이라는 기능을 사용할 수 있다.


상속이란?

상속이란 하위 클래스(자식 클래스)라는 새로운 클래스를 상위 클래스(부모 클래스)를 이용해 생성하는 방법이다.

이는 상위 클래스의 모든 변수와 메소드를 하위 클래스에서 사용할 수 있는 특징이 있다.

그리고 상위 클래스에서 많이 상속시켜서 하위 클래스를 여러 개 만들어도, 상위 클래스는 변하지 않는 특징이 있다.

가장 큰 장점은 중복된 코드를 피할 수 있다는 점에서 재사용성이 좋다.

그리고 상위 클래스의 기능만 수정하면, 하위 클래스의 모든 기능이 한 번에 수정된다는 점에서 유지보수도 좋다.

직접 사용하는 방법을 배워보자.

우선 중복되는 속성을 모두 빼서, pokemon이라는 상위 클래스를 만든다.

중요한 점은 상위 클래스의 변수를 만들 때, private로 만들지 말고 protected로 만들어야 한다.

이유는 나중에 설명하겠다.

class pokemon {
protected:
	// 상위 클래스의 상속줄 변수
	string name;
	int type;
	int hp;
	int attack;
	int defend;
	int mobility;
	struct skill_info skill;

public:
	// 상위 클래스의 상속 줄 메소드
	pokemon(const string& _name, int _type, int _hp, int _attack, int _defend, int _mobility, skill_info _skill)
		: name(_name), type(_type), hp(_hp), attack(_attack), defend(_defend), mobility(_mobility), skill(_skill) {}
	// 모든 변수를 입력 받을 수 있게 생성자를 만드는 것이 중요!

	void info() const {
		cout << name << "의 hp : " << hp << endl;
	}

	string get_name() const {
		return name;
	}

	int get_type() const {
		return type;
	}

	int get_hp() const {
		return hp;
	}

	int get_defend() const {
		return defend;
	}

	int get_mobility() const {
		return mobility;
	}

	int get_accuracy() const {
		return skill.accuracy;
	}

	int get_skill_damage() const {
		return skill.power * attack;
	}

	void recv_attack(int damage) {
		hp -= damage;
	}
};

우리가 클래스를 만들 때, private 혹은 public 두 개의 접근 지정자만 배웠다.

하지만 사실 protected라는 접근 지정자가 있다.

protected상속된 클래스끼리는 접근이 가능하다는 뜻이다.

위의 접근 지정자가 생긴 건 당연한 수순이었는데, private으로 만들어 놓으면 상속된 하위 클래스에서도 위로 접근이 안되기 때문에 중간 정도의 범위를 가진 protected가 필요했다.

즉, 하위 클래스를 만들 것이 아니라면 굳이 protected를 쓸 필요가 없고, 상속을 위한다면 protected로 private을 대신하면 된다.

그리고 이를 상속 받을 때는 아래와 같이 해주면 된다.

class bukein : public pokemon{

public:
	bukein() : pokemon("브케인", Fire, 450, 45, 10, 20, { 8, 50 }) {}
	// 디폴트 생성자

	bukein(string _name) : pokemon(_name, Fire, 450, 45, 10, 20, { 8, 50 }) {}
	// 1변수 오버로딩 생성자

	bukein(const bukein& _target) : pokemon(_target) {
		cout << name << "이(가) 복사되었다" << endl;
	}
	// 복사 생성자

	~bukein() { cout << name << "은(는) 쓰러졌다" << endl; }
	// 소멸자
};

class riako : public pokemon{

public:
	riako() : pokemon("리아코", Water, 500, 35, 20, 10, { 5, 55 }) {}
	// 디폴트 생성자

	riako(std::string _name) : pokemon(_name, Water, 500, 35, 20, 10, { 5, 55 }) {}
	// 1변수 오버로딩 생성자

	riako(const riako& _target) : pokemon(_target) {
		cout << name << "이(가) 복사되었다" << endl;
	}
	// 복사 생성자

	~riako() { cout << name << "은(는) 쓰러졌다" << endl; }
	// 소멸자

}; //riako 클래스

class chicorita : public pokemon{

public:
	chicorita() : pokemon("치코리타", Grass, 550, 40, 30, 0, { 5, 60 }) {}
	// 디폴트 생성자

	chicorita(std::string _name) : pokemon(_name, Grass, 550, 40, 30, 0, { 5, 60 }) {}
	// 1변수 오버로딩 생성자

	chicorita(const chicorita& _target) : pokemon(_target) {
		cout << name << "이(가) 복사되었다" << endl;
	}
	// 복사 생성자

	~chicorita() { cout << name << "은(는) 쓰러졌다" << endl; }
	// 소멸자
}; //chicorita 클래스

하위 클래스를 정의할 때, 옆에 : 접근 지정자 상위 클래스를 적어주면 상속이 완료된다.


상속 접근 지정자

: 뒤에 붙은 접근 지정자는 상속 접근 지정자라고 한다.

이는 상속의 형태를 정해주는 것인데, 상속의 종류로는 private 상속, protected 상속, public 상속이 존재한다.

첫 번째 public 상속 : 상위 클래스의 변수와 메소드의 접근 지정자를 그대로 상속한다.

예를 들어, protected로 선언된 변수와 public으로 선언된 메소드를 그대로 protected와 public으로 각각 상속한다.

두 번째는, private 상속이다. 이는 상위 클래스에서 어떻게 선언되었든 간에, 모두 private 처리로 바꿔버린다.

즉 하위 클래스가 protected의 변수와 public 메소드를 상속은 받지만, private로 접근 지정자가 바뀌어버린다.

마지막은, protected 상속이다. 이는 public만 protected로 바뀌고, 나머지는 같은 접근 지정자로 상속을 받는다.

대부분은 protected나 public 상속을 이용하고, private 상속은 특별한 경우가 아니면 사용하지 않는다.(읽어볼 거리 참고)


Class Diagram으로 보는 상속 구조

Class Diagram에서 상속화살표로 나타나며, 하위 클래스가 상위 클래스를 가리키는 방향으로 상속을 나타낸다.

우리가 수정한 구조는 아래와 같은 Class Diagram으로 나타난다.

부모 클래스를 보고 하위 클래스가 어떤 성질을 가질지 예상할 수 있다는 장점이 있다.


그래서 상속을 이용하면 뭐가 좋은데?

상속의 특징으로는 중복된 코드를 제거하고, 유지보수를 할 수 있다는 장점이 있다.

하지만 단점도 있는 법, 클래스의 계층 구조를 알아야 하므로 상속이 많아지면 복잡성이 커진다.

그 말은 한눈에 봤을 때 이해하기 힘들어지기에, 가독성이 떨어진다는 말이 된다.

그리고 모든 변수와 메소드를 상속받기에, 쓸데없는 특성을 받아야 하거나 호출 시간이 길어질 수도 있다.

그래서 상속을 할 때는 신중해야 한다.

오히려 유지보수 측면에서 복잡해질 수도 있으므로, 너무 많은 상속은 피하도록 하자.

아직 상속에 대해서 반의반도 못 다루었다. 다음 시간에는 상속에 대한 두 번째 이야기를 다루어보겠다.


📖 오늘의 핵심(다 알기 전까지는 넘어가지 말자❗)

✅ 1. 상속이 무엇인지 알고, 그를 구현할 수 있다.

✅ 2. 상속 접근 지정자에 대해 설명할 수 있다.

✅ 3. 상속의 장점과 단점에 대해 안다.

⚠️ 접근 지정자와 상속 접근 지정자의 설정이 굉장히 중요하다. 꼭 이해하도록 하자.

⚠️ private 상속은 가급적 피하자. (읽어볼 거리 참고)

💣 과제, 없음

🔜 더 공부해보기,

읽어볼 거리(1) - private 상속의 이유