C++

[강의] 11월 13일 수업정리

k-codestudy 2024. 11. 14. 02:27

상속에 대한 수업을 들었다.

1. 부모 기능 보장 및 확장

1.1 코드 설명

아래 코드는 C_CHILD 클래스가 C_PARENT 클래스를 상속받고 있으며, C_CHILD 클래스의 test 메서드에서 부모 클래스의 test 기능을 호출하여 부모 기능을 보장하는 동시에 자식의 고유 기능도 확장하는 예제이다.

// parent.h
#pragma once

#include <stdio.h>

class C_PARENT
{
public:
	virtual ~C_PARENT() = default;
	virtual void test();
};


// child.h
#pragma once
#include"parent.h"

class C_CHILD : public C_PARENT
{
public:
	void test() override;
};

 

// parent.cpp
#include "parent.h"

void C_PARENT::test()
{
	printf("부모 기능\n");
}

// child.cpp
#include "child.h"

void C_CHILD::test()
{
	C_PARENT::test();
	printf("자식 기능\n");
}

// main.cpp
#include <iostream>
#include "child.h"

int main()
{
	C_PARENT* pData{};

	pData = new C_CHILD{};
	pData->test();
}

 

1.2 수정된 부분 설명

C_PARENT::test();
  • C_CHILD::test에서 C_PARENT::test()를 호출하여 부모 클래스의 기능을 실행한 후 자식의 고유 기능을 추가로 실행하게 했다.
  • 이렇게 하면 부모 클래스의 기능을 유지하면서 자식 클래스의 기능을 확장한 것이 됩니다.

 

2. 상속의 공통된 부분 관리

 

2.1 코드 설명

아래는 I_ANIMAL이라는 인터페이스를 만들어 C_ANIMAL 클래스와 이를 상속하는 C_DOG 및 C_CAT 클래스를 정의한 코드이다. 여기서 각 동물 클래스는 move 메서드를 구현해 특정 동물의 움직임을 표현한 것이다.

// animal.h
#pragma once

#include <stdio.h>

__interface I_ANIMAL
{
	void move();
};

class C_ANIMAL abstract : public I_ANIMAL
{
public:
	C_ANIMAL() = default;
	virtual ~C_ANIMAL() = default;
	C_ANIMAL(const C_ANIMAL&) = default;
	C_ANIMAL& operator=(const C_ANIMAL&) = default;
};

// dog.h
#pragma once

#include "animal.h"

class C_DOG : public C_ANIMAL
{
public:
	void move() override;
};

// cat.h
#pragma once

#include "animal.h"

class C_CAT : public C_ANIMAL
{
public:


	// C_ANIMAL을(를) 통해 상속됨
	void move() override;

};
// dog.cpp
#include "dog.h"

void C_DOG::move()
{
	printf("네발로 개가 걷는다\n");
}


// cat. cpp
#include "cat.h"

void C_CAT::move()
{
	printf("네발로 고양이가 걷는다\n");
}


// main.cpp
#include <iostream>
#include "dog.h"
#include "cat.h"

int main()
{
    C_ANIMAL* arData[5]{};

    arData[0] = new C_DOG{};
    arData[1] = new C_CAT{};
    arData[2] = new C_DOG{};
    arData[3] = new C_CAT{};
    arData[4] = new C_DOG{};

    for (int i = 0; i < 5; i++)
    {
        arData[i]->move();
    }
}

 

2.2 수정된 부분 설명

  • 여기서 주의 깊게 봐야 하는 부분은 "네발로"의 부분이다.
    (주의점 : 만들다 보니 공통된 부분이 생긴 것이지, 공통된 부분을 관리하려고 상속을 받은 것이 아니다.)
  • C_ANIMAL::move() 메서드에 "네발로"라는 공통된 기능을 구현, 이를 통해 C_DOG, C_CAT 클래스에서 호출하도록 했다.
  • 이 방식은 부모 기능을 보장하면서 확장하는 개념으로, 순수 가상 함수(인터페이스)와 구현을 분리해 공통 기능을 적절히 사용할 수 있게 해 준다.

2.3 주의 사항

  • 모든 동물에게 공통된 동작이 존재하는 것은 아니다. 예를 들어 물고기와 같은 다른 동물들은 다르게 움직이기 때문에 반드시 공통 기능이 필요한 경우 사용하는 것이 좋다.
  •  이름이 똑같고 네임스페이스를 사용한다 라면 상속이고 부모를 보장하면서 확장한다는 것을 보고 알아야 한다.
// animal.h
#pragma once

#include <stdio.h>

__interface I_ANIMAL
{
	void move();
};

class C_ANIMAL abstract : public I_ANIMAL
{
public:
	C_ANIMAL() = default;
	virtual ~C_ANIMAL() = default;
	C_ANIMAL(const C_ANIMAL&) = default;
	C_ANIMAL& operator=(const C_ANIMAL&) = default;
	virtual void move();
};

// animal.cpp
#include "animal.h"

void C_ANIMAL::move()
{
	printf("네발로 ");
}

// dog.cpp
#include "dog.h"

void C_DOG::move()
{
	C_ANIMAL::move();
	printf("개가 걷는다\n");
}


// cat.cpp
#include "cat.h"

void C_CAT::move()
{
	C_ANIMAL::move();
	printf("고양이가 걷는다\n");
}

 

 

3. 상속 방법론 

3.1 설명

상속을 언제 사용하는지에 대한 2가지 중요 개념이 존재한다.

  • 유스케이스 이론 : 상속을 통해 특정 객체의 본질과 사용법을 정의한다.
  • 동사의 확장 : 상속은 기능(동사)만 상속받아야 하며, 명사(예: 특정 종류의 행동이나 사물)와 같은 구체적 개념을 상속을 하면 안 된다.

 

 

3.2 예시 코드

 

예시로 C_DOCK 클래스를 상속받아 오리가 걸어 다니거나 수영하고, 날아다니는 여러 방식의 오리 클래스가 정의한다.

// dock.h
#pragma once

#include <stdio.h>

__interface I_DOCK
{
	void move();
};

class C_DOCK : public I_DOCK
{
public:
	C_DOCK() = default;
	virtual ~C_DOCK() = default;
	C_DOCK(const C_DOCK&) = default;
	C_DOCK& operator=(const C_DOCK&) = default;
};

// flydock.h
#pragma once

#include "dock.h"

class C_FLYDOCK : public C_DOCK
{
public:
	void move() override;
};

// swimdock.h
#pragma once

#include "dock.h"

class C_SWIMDOCK : public C_DOCK
{
public:
	void move() override;
};

// walkdock.h
#pragma once

#include "dock.h"

class C_WALKDOCK: public C_DOCK
{
public:
	void move() override;
};

 

// flydock.cpp
#include "flydock.h"

void C_FLYDOCK::move()
{
	printf("날아다니는 오리\n");
}

// swimdock.cpp
#include "swimdock.h"

void C_SWIMDOCK::move()
{
	printf("수영하는 오리\n");
}

// walkdock.cpp
#include "walkdock.h"

void C_WALKDOCK::move()
{
	printf("걸어다니는 오리\n");
}

// main.cpp
#include <iostream>
#include "flydock.h"
#include "swimdock.h"
#include "walkdock.h"

int main()
{
	C_DOCK* pDock[3]{};

	pDock[0] = new C_FLYDOCK{};
	pDock[1] = new C_SWIMDOCK{};
	pDock[2] = new C_WALKDOCK{};

	for (int i = 0; i < 3; i++)
	{
		pDock[i]->move();
	}

}

이 예시에서 걸어 다니는 오리, 수영하는 오리, 날아다니는 오리 각각의 move 메서드가 다르게 구현되어 있다.

만약 여기서 소리 기능 (음악, 멜로디, 꽥소리) 3가지 기능이 추가된다고 하면 몇 개의 클래스를 만들어야 할까?

답은 9개이다. 이유는 기존 상속에 있던 것에 새로 추가되는 기능을 곱해서 이 값이 나오게 된다.

즉 기능을 추가하게 되면 클래스가 급격히 늘어날 수 있는 것이다.

그렇기에 기능 분리 원칙에 따라 기능을 기준으로 잡고 상속을 받지 말아야 한다.

기능 분리 원칙에 따라 동사만 상속받고 기능을 조합하는 방식으로 해결 가능하다.

'C++' 카테고리의 다른 글

[강의] 11월 15일 수업정리  (0) 2024.11.17
[강의] 11월 14일 수업정리  (8) 2024.11.15
[강의] 11월 12일 수업정리  (6) 2024.11.13
[강의] 11월 8일 수업정리  (1) 2024.11.10
[강의] 11월 7일 수업정리  (1) 2024.11.08