Design Pattern Warming UP

EHminShoov2J·2024년 10월 5일
0

Design Pattern

목록 보기
1/6
post-thumbnail

1. Constructor

1.1. 생성자 : 객체가 생성될 때 호출되는 특별한 함수로 주로 객체를 초기화하는 데 사용.


1.2. 부모클래스의 생성자 호출

  • 자식 클래스의 생성자를 호출하는 경우 부모 클래스의 기본 생성자가 자동으로 호출 되며, 부모 클래스의 생성자가 존재하지 않는 경우 Error가 발생.
  • 부모 클래스의 기본 생성자가 아닌 다른 생성자를 호출하고 싶은 경우 명시적 으로 선언해 주어야 함.
    class Derived : public Base
    {
    public:		
    //	Derived() { }     // Derived() : Base(){ }
    //	Derived(int a) { }// Derived(int a) : Base() { }
    	Derived()      : Base(0) { }
    	Derived(int a) : Base(a) { }
    };

1.3. Protected 생성자

  • 부모 클래스의 생성자중 Protected로 선언된 생성자는 직접적으로 호출이불가능하나, 상속하는 것은 가능.
  • 이는 개념(Animal)에 해당하는 부모 클래스를 생성하는 것을 막아 현실 세계를 더욱 사실적으로 모델링 할 수 있음.
class Animal
{
protected: // protected는 상속에 열려 있으나 호출에 막혀 있다.
	Animal() {}
};
class Dog : public Animal
{
public:
	Dog() {}	// Dog() : Animal() {}
};
int main()
{
	Animal a;	// A error protected 생성자를 직접 호출 하는 case
	Dog    d;	// B ok
}

2. Upcasting

2.1. upcating

  • 자식 클래스(파생 클래스, derived class)의 객체를 부모 클래스(기반 클래스, base class)의 포인터 또는 참조로 변환하는 과정
  • 단 이때 부모 클래스의 포인터로는 부모 클래스의 맴버만 접근할 수 있음
  • 만약 자식 클래스의 맴버에 접근하고 싶다면 명시적 변환을 활용해야 한다.
static_cast<Rect*>(p)->x = 0; // ok

2.2. Upcasting 활용

  • 동종을 보관하는 Container로 활용.
  • 모든 자식 클래스에 공통으로 적용되는 함수를 한번에 적용
class Shape
{
public:
    int color;
};

class Rect : public Shape
{
public:
    int x, y, w, h;
};

void changeBlack(Shape* p) // 공통함수 한번에 적용
{
    p->color = 0;
}

/*
void changeBlack(Triangle* p)
{
    p->color = 0;
}
*/
int main()
{
    Rect r;
    changeBlack(&r);

    Rect* buffer[10];
    Shape* buffer[10]; //동종을 보관하는 Container
}

3. Dynamic Cast

3.1. Downcasting

  • 부모 클래스의 객체를 자식 클래스의 포인터 또는 참조로 변환하는 과정
  • 명시적인 변환이 필요하다. (Static or Danamic Casting)

3.2. Static Casting 과 Dynamic Casting

  • Static Casting
    • 컴파일 시간 Casting
    • 런타임에 오버헤드가 발생하지 않는다.
    • 컴파일 시간에는 Casting하려는 Memory가 무엇인지 확인할 수 없기 떄문에 잘못된 DownCasting인지 확인 불가능
  • Dyanmaic Casting
    • 런타임 Casting
    • 런타임 오버헤드 발생
    • Casting 하려는 메모리 확인후 진행하여 잘못된 DownCasting이 발생하지 않는다. 단 메모리 확인을 위해 가상함수 테이블을 활용하기 때문에 가상함수가 존재해야만한다.

3.3. Dynamic Casting의 설계적 관점

  • Upcasting을 통해 부모 클래스(Animal)에서 정의된 함수가 있다면, 모든 자식 Classd에서 수행 가능해야 좋은 설계라고 할수 있음.
  • 따라서 굳이 Dynamic Casting을 통해 특정 자식객체인지 확인할 이유가 없다.
  • 만약 특정 자식객체만 독특하게 수행해야할 것이 있다면 오버로딩을 통해 새로운 함수를 만들어 내는 것이 설계적으로 바람직함
//기존의 코드를 수정하지 않고, 추가적인 요구사항을 만족시키기 위해 다음과 같이 오버라이딩을 진행
void foo(Animal* p)
{
	// 모든 동물의 공통의 작업
}

void foo(Dog* p)
{
	foo(static_cast<Animal*>(p));
	p->run();
}

위의 코드와 같이 기존의 코드는 건드리지 않고 (Closed) 추가적인 기능을 수행(Open)할 수 있는 것이 바람직하다.

4. Abstract

4.1. 가상함수 & 순수 가상함수

  • 가상함수 : virtual 키워드가 앖에 먼저 선언되며, 자식 클래스에서 Overriding이 가능한 함수
  • 순수 가상함수 : virtual 키워드로 시작하며, 구현부 대신 =0
    으로 구성되어진 함수
    - 자식 클래스에서 해당 함수를 반드시 override 하도록 강제 (구현하지 않는경우 컴파일 되지 않음)
    - 순수 가상함수가 하나라도 포함한 클래스는 추상 클래스가 됨. (추상 클래스는 객체를 생성할 수 없으나, 포인터 생성은 가능)

4.2. 설계 관점

  • 가상함수의 경우 자식 클래스에서 해당 함수를 재정의 하지 않으면 부모 클래스의 함수를 호출 가능. 하지만 이때 부모 클래스의 함수를 호출하는 것이 개념적으로 맞지 않을 수 있음.
class Shape
{
public:
	virtual ~Shape() {}
	virtual void draw() = 0; // 추상적인 개념을 직접 그려낸다는게 어려움.
};

class Rect : public Shape 
{
public:
	void draw() override { std::println("draw Rect"); }
};
  • 위의 코드와 같이 Shpae 라는 개념이 그려질(draw)될 일이 없으므로 순수 가장함수로 만들고, 자식 클래스에서 override 하여 그리는 방식 활용

5. Interface

5.1. 인터페이스 & 추상 클래스

  • 인터페이스 : 모두 순수 가상 함수들로 구현 되어 지켜야할 규칙 만을 가진 클래스 (구현 한다고 표현)
  • 추상 클래스 : 순수 가상함수를 하나라도 포함하는 클래스

* Struct를 이용해서 인터페이스를 생성하기도 함. (public 키워드 뺄 수 있음)


5.2. virtual 키워드

  • virtual 키워드는 런타임 시에 실제 객체 타입에 따라 함수를 선택적으로 호출 되도록 보장. (CPP 진영에서는 "가상함수가 아니면 재정의 하지도 말라"라는 격언이 존재)
  • 이런 이유로 interface 혹은 기반 클래스들은 가상 소멸자를 사용해 주는 것이 좋다.(Upcasting을 사용하여 부모 클래스의 포인터로 파생클래스의 객체를 참조하는 경우 부모 클래스의 소멸자가 호출되는데, 이때 자식 클래스의 맴버변수들에 대한 정보 누락으로 메모리 누수가 발생할 수 있음)
struct ICamera
{	
//public:
	virtual ~ICamera() {} // 가상 소멸자.

	virtual void take() = 0;	
};
  • 동일한 코드가 상황에 따라 다르게 동작하는 것을 다형성(Polymorphism) 이라고 하며 이는 OCP를 지키는데 큰 도움이 된다.

5.3. 강한결합 & 약한결합

  • 강한 결합 : 서로의 클래스 이름을 알고 있는 상태로 교체가 불가능하고 경직적인 디자인
  • 약한 결합 : 서로의 클래스명을 알지 못하는 상태로 교체가 가능하고 확장성 있는 유연한 디자인.

0개의 댓글