-
[객체의 관계] Is-A 그리고 Has-A 관계 !뜯고 또 뜯어보는 컴퓨터/씨쁠쁠 C++ 2022. 11. 8. 02:03반응형
프로그래밍을 하다 보면, 서로 다른 클래스가 공통적인 속성을 가지거나, 최소 두 클래스가 관련이 되어있는 경우가 존재합니다. 이때, 객체의 관계를 표현할 수 있는데, 대표적으로 Is-a 관계 그리고 Has-a 관계가 있습니다.
Is-a 그리고 Has-a 관계 그게 뭔데?
Has-a 관계
Composition 또는 Aggregation 관계라고도 부르는 has-a 관계는 "A는 B를 가진다" 또는 "A가 B를 가지고 있다"라고 정의할 수 있습니다. 한 객체가 다른 객체의 일부가 되는 것입니다.
Has-a 관계는 비행기의 엔진을 예로 들 수 있는데, 비행기에는 엔진이 존재하며, 엔진을 포함하고 있다 또는 비행기는 엔진이 있다라고 표현할 수 있습니다. 이를 코드로 표현하면, 비행기라는 객체는 엔진 컴포넌트를 가진다고 표현할 것입니다.
class engine{ public: int enginePower = 0; engine(int _enginePower) : enginePower(_enginePower){} }; class AirPlane{ private: engine mEngine; public: int id; string name; AirPlane(int i, string &nm, engine& _engine) : id(i), name(nm), mEngine(_engine){} };
Is-a 관계
is-a 관계는 흔히 말하는 OOP 의 핵심 개념 중 하나인 상속(Inheritance)과 관련이 많습니다. 상속은 파생, 서브 클래스, 확장 등 다양하게 표현됩니다. 클래스는 현실세계가 여러 가지 속성과 동작을 가진 객체로 구성된다는 점을 모델링한 것이고, 상속은 이러한 객체가 주로 계층 구조를 띈다는 점에서 착안하여 모델링한 것입니다.
기본적으로 상속 관계는 "A는 일종의 B다"라고 표현하게 되는데, 예를 들어 펭귄, 고양이, 캥거루 등등이 가진 공통점을 묶어 모든 동물이 공통적으로 가지는 크기, 위치 등과 같은 모든 속성(property)과 모든 동작을 Animal이란 클래스로 묶어 정의할 수 있습니다. 따라서, 앞서 말한 펭귄 고양이, 캥거루 등과 같은 객체는 Animal 클래스의 파생 클래스(derived class)로 정의하게 됩니다.
클래스를 is-a 관계로 묶으려면 공통 기능을 베이스 클래스로 묶어서 다른 클래스가 확장할 수 잇게끔 해야 합니다. 상속의 기능에는 "기능 추가", "기능 변경(override)", "특징 추가(method add)"의 특징이 있을 수 있습니다. 상속을 활용하여 다형성(Polymorphism)을 구현이 가능하므로, is-a 관계의 특징을 잘 알아두시는 것이 좋습니다.
반응형그래서 is-a / has-a 뭐 써야 되는데?
현실에서는 손쉽게 is-a 그리고 has-a 관계를 구분할 수 있지만, 프로그래밍을 할 시에는 이 관계가 불분명할때가 많다. 여기서 LSP(리스 코프 치환 원칙)을 적용할 수 있습니다. 여기서 리스 코프 치환 원칙은 '동작을 바꾸지 않고도 베이스 클래스 대신 파생(derived) 클래스를 사용할 수 있어야 한다"는 원칙입니다.
리스코프 치환 원칙이란
"동작을 바꾸지 않아도, 베이스 클래스 대신 파생 클래스를 사용할 수 있어야 한다"
는 원칙입니다.따라서, LSP 원칙을 위반한다면 Has-a 관계로 설명할 수 있고, LSP 원칙을 위반하지 않는다면 Is-a 관계로 설계할 수 있을 것입니다. 가장 쉬운 예는 Square 그리고 Rectangle 예제를 들 수 있는데, 수학적으로는 Rectangle이 Square의 일부로 Is-a관계로 상속으로 표현 가능할 것 같습니다. 코드로 표현하면 다음과 같습니다.
class Rectangle { protected: int width, height; public: Rectangle(const int width, const int height) : width{ width }, height{ height } { } int get_width() const { return width; } virtual void set_width(const int width) { this->width = width; } int get_height() const { return height; } virtual void set_height(const int height) { this->height = height; } int area() const { return width * height; } }; class Square : public Rectangle { public: Square(int size) : Rectangle(size, size) {} void set_width(const int width) override { this->width = height = width; } void set_height(const int height) override { this->height = width = height; } }; void process(Rectangle& r) { int w = r.get_width(); r.set_height(10); } int main() { Rectangle r{ 5,5 }; process(r); Square s{ 5 }; process(s); getchar(); return 0; }
하지만 이렇게 된다면, Process 에서 Square과 Rectangle 사이에서 불일치가 되므로, Rectangle과 Square 은 LSP 원칙에 위배되게 됩니다. 따라서, 설계를 분리하여, Shape( ) 형태를 상속받아, Rectangle과 Square이 따로 분리되어 있도록 설계하는 방식으로 해야겠습니다.
긴 글 읽어주셔서 감사합니다.!
도움이 되거나 잘못된 내용이 있다면, 댓글 부탁드리겠습니다!
반응형'뜯고 또 뜯어보는 컴퓨터 > 씨쁠쁠 C++' 카테고리의 다른 글
[c++] 너 .. 혹시 생성자에서 가상함수 호출할거야 ? (2) 2022.11.09 [c++] New 그리고 malloc( ) 구분할 수 있어? (0) 2022.11.08 [c++] 상속을 어떻게 멈출 수 있을까 ? (0) 2022.11.06 왜 c에는 이동 연산자가 없는가. (0) 2022.11.03 Vtable - 어디에 존재하는가.. (0) 2022.11.03