Back-end/C++

22. 03. 24 - 상속(특수화) : 동물 육성 게임, string 클래스 기능 추가 상속

giggs 2022. 4. 2. 14:21

 

 

상속을 하는 2가지 – 일반화, 특수화

 

 

 

일반화

  • 기획단계에서 상속을 사용할지 말지 결정한다.
  • 초반에 보고 상속을 하면 되겠다.
  • 작업하다 보니 상속을 하면 되겠다.
  • 하고 상속을 판단하여 구조를 짜는 경우

 

 

특수화

  • 개발과정에서 상속을 사용할지 말지 경정한다.
  • SRP – 클래스는 한 가지의 기능 책임을 지는 것이다.
  • 근데 개발하다 보니 클래스가 너무 비대해진다.
  • 이것을 기능 별로 쪼개서 분리하고 상속하는 구조를 짜는 경우

 

 

 

 

 


 

 

 

 

상속 (특수화) 실습  

 

상황 1.

기획자가 등장 동물 - 3가지밖에 없다고 단정 / 클래스 하나로 다 만드는 것이 났겠다 판단

 

 

 

Animal 클래스 하나에 돼지, 소, 닭 - 다 만들 것이다.

 

 

#include <iostream>
#include <string>

using namespace std;

class Animal {
private:

	string _name;
	int _type;
	float _age;
	float _weight;
	float _height;
	bool _isFly;


public:
	Animal(string name, int type, float age, float weight, float height, bool isFly, bool isSwim)
		:_name(name), _type(type), _age(age), _weight(weight), _height(height), _isFly(isFly), _isSwim(isSwim) {}

	~Animal() {}


	void Speak() {
		switch (_type) {

		case 0:
			cout << _name << "가 꿀꿀 합니다." << endl;
			break;

		case 1:
			cout << _name << "가 음매 합니다." << endl;
			break;

		case 2:
			cout << _name << "가 꼬끼오 합니다." << endl;
			break;

		}
	}


	void Run() {
		if (_isFly) {
			cout << _name << "이 납니다" << endl;
		}
		else {			
			cout << _name << "이 뜁니다" << endl;
		}
	}
	


	void Eat() {
		cout << _name << "이 먹습니다." << endl;
	}

};

 

 

 

1- type; : 이 클래스로 돼지, 소, 닭, 소, 만들어 줄 것이므로 타입 필요

2- 필요한 멤버 변수들 해주고.

3- 생성자/ 소멸자 생성

4- 객체별로 다르게 적용되는 함수 필요분기문으로

4.1 – 돼지, 소, 닭 - 다르게 말한다. - 각각 speak( ) 다르게 적용 필요

4.2 - 돼지, 소, 닭 - 뛰거나 난다 - 각각 run( ) 다르게 적용 필요

 

5- main에서 객체 생성한 뒤 함수 호출해서 test

 

 

int main() {

	Animal pig("pig", 0, 1.2f, 200.0f, 100.0f, false, false);
	Animal cow("cow", 1, 2.3f, 300.0f, 170.0f, false, false);
	Animal noFlyChicken("noFlyChicken", 2, 0.8f, 3.0f, 30.0f, false, false);
	Animal flyChicken("FlyChicken", 2, 0.8f, 3.0f, 30.0f, true, false);
	
	pig.Speak();
	cow.Speak();
	noFlyChicken.Speak();
	flyChicken.Speak();
	
	cout << endl;

	pig.Run();
	cow.Run();
	noFlyChicken.Run();
	flyChicken.Run();
	

	return 0;
}

 

 

정상출력 :D

상황 1 종료! 

 

 

 


 

 

 

상황 2

- 이렇게 다해놨는데, 기획자가 와서 요새 애들이 돌고래 좋아한다고 추가해달라고 한다..

 

 

 

돌고래 추가한다...

 

#include <iostream>
#include <string>

using namespace std;

class Animal {
private:

	string _name;
	int _type;
	float _age;
	float _weight;
	float _height;
	bool _isFly;
	bool _isSwim;

public:
	Animal(string name, int type, float age, float weight, float height, bool isFly, bool isSwim)
		:_name(name), _type(type), _age(age), _weight(weight), _height(height), _isFly(isFly), _isSwim(isSwim) {}

	~Animal() {}


	void Speak() {
		switch (_type) {

		case 0:
			cout << _name << "가 꿀꿀 합니다." << endl;
			break;

		case 1:
			cout << _name << "가 음매 합니다." << endl;
			break;

		case 2:
			cout << _name << "가 꼬끼오 합니다." << endl;
			break;

		case 3:
			cout << _name << "가 끽끽 합니다." << endl;
			break;
		}
	}

	void Run() {
		if (_isFly) {
			cout << _name << "이 납니다" << endl;
		}
		else {
			if (_isSwim) {
				cout << _name << "이 헤엄칩니다." << endl;
			}
			else {
				cout << _name << "이 뜁니다" << endl;
			}
		}
	}


	void Eat() {
		cout << _name << "이 먹습니다." << endl;
	}


};


int main() {

	Animal pig("pig", 0, 1.2f, 200.0f, 100.0f, false, false);
	Animal cow("cow", 1, 2.3f, 300.0f, 170.0f, false, false);
	Animal noFlyChicken("noFlyChicken", 2, 0.8f, 3.0f, 30.0f, false, false);
	Animal flyChicken("FlyChicken", 2, 0.8f, 3.0f, 30.0f, true, false);
	Animal dolphin("dolphin", 3, 0.8f, 3.0f, 30.0f, false, true);


	pig.Speak();
	cow.Speak();
	noFlyChicken.Speak();
	flyChicken.Speak();
	dolphin.Speak();


	cout << endl;

	pig.Run();
	cow.Run();
	noFlyChicken.Run();
	flyChicken.Run();
	dolphin.Run();

	return 0;
}

 

 

 

1 - 돌고래 타입 추가해준다.

2 - 수영하는지 안 하는지 멤버 변수 추가

3 - 체크하기 위해 생성자 초기화 리스트 뒤에 추가

4 - speak( ) 부분에 case 추가 

5 - run( ) 부분 if - else 조건 수정

6 - 객체 생성 후 테스트

 

 

돌고래 추가 완료 : 상황 2 종료!

 

 

 

 


 

 

상황 3

이렇게 다해놨는데, 기획자가 와서 둘리가 카메오로 등장하게 해 달라 요청

 

 

이런 식으로 이질적인 애가 자꾸 추가되게 되면,

걔가 사용하는 멤버 변수도,, 생성자도, 함수도, 분기문도, 다 추가 및 수정해줘야 한다..

이럴 경우 - 상속 사용할지 생각 필요

 

 

 

 

이럴 때 사용하는 상속이 : 특수화 상속!

 

 

 


 

 

 

 

상속 사용하기로 판단 - 상속(특수화) 시작

 

 

분리 시작

 

 

1. type을 없애는 것에 초점이 맞춰질 것이다.

 

2. 일반적인 부분은 놔두고 - Pig, Cow, Chicken, Dolphin 분리

 

3. 빼낸 Class 부분 - 생성자/함수 수정 ,

  • 일반적인 거는 부모로 넘기고 특수한 조건은 - 자기 클래스에서 추가, 수정(Speak() , isFly, isSwim 같은)

 

4. Animal 클래스 부분- 뺄꺼빼주고 남은 것들 중 일반적인 것들만 남겨놓고 삭제.

  • 다 빼줬으면 일반적인 내용으로 수정 ( Speak ( ) 부분 - 음매/꼬끼오/끼끽 빼고 -> 말합니다로 초기화 등)

 

5. main( ) 부분 테스트 – 객체 생성 부분 수정 

  • ( Animal Pig ( ) 애니멀 타입의 pig 가 아닌 -> Pig pig로! )

 

 

#include <iostream>
#include <string>

using namespace std;

//부모 클래스
class Animal {
protected:

	string _name;
	float _age;
	float _weight;
	float _height;


public:

	Animal(string name, float age, float weight, float height)
		:_name(name), _age(age), _weight(weight), _height(height) {}

	~Animal() {}


	void Speak() {		
		cout << _name << "가 말합니다." << endl;			
	}
	

	void Run() {
		cout << _name << "이 뜁니다" << endl;		
	}


	void Eat() {
		cout << _name << "이 먹습니다." << endl;
	}


};


//돼지
class Pig : public Animal{

public:
	Pig(string name, float age, float weight, float height)
		: Animal(name, age,weight,height) {}

	~Pig() {}

	void Speak() {		
			cout << _name << "가 꿀꿀 합니다." << endl;			
	}
};


//소
class Cow : public Animal{

public:
	Cow(string name, float age, float weight, float height)
		: Animal(name, age, weight, height) {}

	~Cow() {}

	void Speak() {		
		cout << _name << "가 음매 합니다." << endl;			
	}
};


//닭
class Chicken : public Animal{
private:
	bool _isFly;
	
public:
	Chicken(string name, float age, float weight, float height, bool isFly)
		// ,_isFly(isFly) 해주는 부분 체크
		: Animal(name, age, weight, height), _isFly(isFly) {}

	~Chicken() {}


	void Speak() {		
		cout << _name << "가 꼬끼오 합니다." << endl;			
	}

	void Run() {
		if (_isFly) {
			cout << _name << "이 납니다" << endl;
		}
		else {
			cout << _name << "이 뜁니다" << endl;
		}		
	}
};


//돌고래
class Dolphin : public Animal {

public:
	Dolphin(string name, float age, float weight, float height)
		: Animal(name, age, weight, height) {}

	~Dolphin() {}


	void Speak() {
		cout << _name << "가 끽끽 합니다." << endl;
	}

	
	//Run 부분, 생성자 부분 치킨이랑 다른 점 체크 - 
	void Run() {
		Swim();
	}

	private:
		void Swim() {
			cout << _name << "이 헤엄칩니다." << endl;
		}
};



int main() {
	
	Pig pig("pig", 1.2f, 200.0f, 100.0f);
	Cow cow("cow", 2.3f, 300.0f, 170.0f);
	Chicken noFlyChicken("noFlyChicken", 0.8f, 3.0f, 30.0f, false);
	Chicken flyChicken("FlyChicken", 0.8f, 3.0f, 30.0f, true);
	Dolphin dolphin("dolphin", 0.8f, 3.0f, 30.0f);

	pig.Speak();
	cow.Speak();
	noFlyChicken.Speak();
	flyChicken.Speak();
	dolphin.Speak();
    
	cout << endl;

	pig.Run();
	cow.Run();
	noFlyChicken.Run();
	flyChicken.Run();
	dolphin.Run();


	return 0;

}

 

 

 

상속은 도구다.

 

 

공통된 내용을 뽑아내서 클래스를 만들고 상속시키는 것

- 상속 일반화 구조작업 때 주로 사용

 

 

 

클래스를 만들었는데, 클래스 하나가 여러 기능을 하려고 하니까type을 쓰고 분기문이 추가된다.

복잡해지면서 비효율적이 되고,, 크기가 커진다 

공통된 부분 빼내서 독립  - 상속 특수화 

일반적인애 말고 특수한애들 뽑아내서 상속화 – 리팩토링

 

 

 

 


 

 

 

+ Check Point 

 

 

 

▣ 상속은 여러 번 사용 가능하다. - 상속 클래스마다 소멸자 필요하다.

 

 

 

 


 

 

 

 

▣ 상속받은 클래스로 객체를 만들면 -> 생성자, 소멸자 생기는 순서 check

  • 부모 생성장 만들어지고, 자식 생성자
  • 자식 소멸자 만들어지고, 부모 소멸자

 

 

 

콘솔 출력 부분 Check

 

 

 

 


 

 

 

 

 

▣ 이전 개발자가 만들어놓은 클래스가 있고, 이미 이 클래스를 여러 곳에서 많이 사용하고 있는 경우 - 기능 추가

  • 기존의 클래스 자체를 수정할 수 없다.  - 그런데 기능이 아쉽다. ( e.g 나누기가 없다 )
  • 이럴 경우 상속을 통해 기능을 확장 생각해볼 수 있다.

 

 

 

#includ <iostream>

using namespace std;


class Math {
public:
	
	int add(int a, int b) {
		return a + b;
	}

	int sub(int a, int b) {
		return a - b;
	}

	int mul(int a, int b) {
		return a * b;
	}
    
};

 

 

 

 

Math 데이터 타입으로 객체를 만들면 3개밖에 못쓴다 ( 나누기 없다 )

 

 

 

Math 데이터 타입의 객체 math 는 Math클래스에 있는 3개의 기능만 사용 가능

 

 

 

 

나누기 기능 추가  Math 상속받은 ExtMath를 만들어 준다.

 

 

 

( Math상속받은 ) ExtMath 데이터 타입의 객체 math 는 Math클래스에 있는 3개의 기능 + ExMath의 기능 사용 가능

 

 

 

 

Math 가 아닌 Math를 상속받아서 기능 추가한 ExtMath로

  • - Math의 기능 3가지에 ExtMath 기능 나누기 추가해서 4개 기능 사용 가능!
  • - 이처럼 기능 추가의 목적으로 상속을 사용하기도 한다!

 

 

 

 


 

 

 

 

기능 추가 개념의 상속 한 번 더- string 클래스의 기능 추가하기

 

 

 

기본 제공 string 클래스의 기능을 이어받으면서 -( 상속받으면서 )

string의 문자열 개수를 세어주는 기능 추가 함수 만들기

 

 

 

 


 

 

 

String 클래스의 기본 개념 잡고 가기

 

▣ String class에 만들어진 소스를 변경하거나 추가하는 것이 불가능

 

 

▣ 문자열 저장이라는 것은 동적으로 돌아가는 것.

  • string a(“monster”); 했을 경우
  • a 객체에는 monster가 들어있는 것이 아니라 "monster" 저장되어있는 공간의 선두 번지 주소 값이 있는 것이다.

 

 

▣ c_str( ) ----> string에 있는 함수 - 문자열이 저장되어있는 배열의 선두 번지 주소 값 받아오는 함수

 

 

▣ 문자를 저장하는 마지막 배열에는 특수문자 '\0' 가 있다 - 널 종단 분자 - 마지막 부분이라는 것 표시해준다.

 

 

 

 


 

 

 

 

문자 배열의 주소 값을 가져와서 c_str( ) , 그 공간에 문자 배열이 있으면 참 해주고 한 칸 이동 *str++

 

 

 

 

6번 Line - string 상속받은 extString 클래스 생성

8번 Line - 인자로 받은 문자 배열의 주소 값을 부모 string( )으로 전달

11번 Line - extString 클래스의 추가 기능 구현

15Line -  *str++  ( *포인터 연산자와, 후위 연산이라는 점 check )

  • string 문자열 있으면 count++해주고 ( 후위 연산 - 주소 값++ 의 의미는 다음칸으로 이동) *str++
  • 이렇게 반복하다가 - 문자 배열의 마지막 0 만나면 거짓으로 while 빠져나오고 count 출력

 

 

 

 


 

 

 

 



review



상속 특수화에 대한 내용을 실습을 통해 알아보았다.
실제 상황?을 예시로 들어서 진행한 부분이
이제 나도 개발자로 일을 하게 되면 저렇겠구나
느껴져서 더 집중할 수 있었다.


개발을 하다 보니 새로 들어오는 요청사항이나 추가되는 기능들로 인해
하나의 클래스에서 너무 많은 기능을 책임지고 있다.
너무 비대해진 클래스의 기능을 따로 빼내서
그것을 상속받는 구조로 만들어 주는 것이 - 특수화 상속!


내가 알던 상속의 개념이었던 일반화와 다른 상속 특수화 새로웠다 ㅎㅎ

- 개발하는 과정 중에 비대해진 클래스의 기능을 분리하기 위한 상속 
- 이미 너무 많은 곳에서 사용하고 있는 클래스를 수정할 수 없으니까
그 클래스를 상속받아서 기능을 추가하는 기능 추가적 상속

개발자가 실제 사용하는 상속의 경우인 것 같아서 재미있었다.

## Check Point --- c_str( ) 부분 , *str++ 부분