디자인 패턴의 개념, protected constructor, upcasting, coupling, 객체지향 등에 대해 살펴보겠습니다.

Intro

디자인 패턴이란?

  • 자주 사용되는 코딩 패턴에 이름을 부여 한 것

  • 이름이 있기 때문에 개발자 간에 편리하고 명확한 의사 소통이 가능해짐

  • GoF’s Design Pattern

    • 1995년 발간
    • 저자들이 기존에 존재하는 코딩 방식에 이름을 부여 함
    • 23개의 디자인 패턴을 설명

본 시리즈 소개

  • 객체 지향 디자인 패턴의 핵심 원리
  • GoF’s 디자인 패턴 책에 소개되는 주요 패턴
  • C++ 진영의 오픈소스가 자주 사용하는 디자인 패턴

디자인 패턴 이해시 필요한 기본 지식

Protected Constructor

  • 생성자를 protected로 만들면, 객체 생성 불가(추상적 개념 필요시 활용)
class Animal
{
protected:
    Animal() {}
};

class Dog : public Animal
{
public:
    Dog() {} // Dog() : Animal() {}
};

int main()
{
    Animal a;   // error
    Dog    d;   // ok.
}
  • Protected 소멸자를 갖는 객체를 만들면, 스택에 생성되는 타입의 객체 생성 불가 (힙에는 객체 생성 가능) 참조 count 기반의 객체 수명 관리 기법에 주로 활용됨
#include <iostream>
using namespace std;

class Car
{
public:
    Car() {}

    void Destroy() { delete this;}
protected:
    ~Car(){ cout << "~Car" << endl;}
};

int main()
{
    //Car c; // 스택에 객체를 만들수 없다.

    Car* p = new Car;
    p->Destroy();

    //delete p;
}

Upcasting

  • 부모 클래스 타입의 포인터(참조)로 자식 클래스 객체를 가르킬 수 있음 (Composite 패턴에 활용)
class Animal
{
    int age;
};
class Dog : public Animal
{
    int color;
};

int main()
{
    Dog d;

    Dog*    p1 = &d;  // ok.

    double* p2 = &d;  // error.

    Animal* p3 = &d;  // ok.
}
  • 부모의 포인터로 자식의 override된 함수 호출(virtual, 다형성)
#include <iostream>
using namespace std;

class Animal
{
    int age;
public:
    virtual void Cry() { cout << "Animal Cry" << endl;}
};

class Dog : public Animal
{
    int color;
public:
    // override
    virtual void Cry()  { cout << "Dog Cry" << endl;}
};

int main()
{
    Dog d;
    Animal* p = &d;

    p->Cry();
}

추상

  • 순수 가상 함수(pure virtual function)
    • 함수 선언 뒤에 = 0를 표기 후 구현하지 않은 가상 함수
    • 함수의 구현부X
  • 추상 클래스(abstract class)
    • 순수 가상 함수를 한 개 이상 가지고 있는 클래스
    • 객체 생성 불가
    • 포인터 변수는 생성 가능
  • 추상클래스의 파생 클래스
    • 순수가상함수의 구현부를 제공하지 않은 경우, 파생 클래스도 추상클래스
class Shape  // 추상 클래스.
{
public:
    virtual void Draw() = 0; // 순수 가상함수.

};
class Rect : public Shape
{
public:
};
virtual void Draw() {} // 구현부를 제공하면 추상 아님.

int main()
{
//    Shape  s; // error.
    Shape* p; // ok..

    Rect r; // Draw()구현이 없으면 error
}

Interface & coupling

  • HDCamera가 새로추가됬을때 기존의 People 코드에(useCamera) 수정이 필요한 문제가 생김(개방 폐쇄의 법칙 위배)
  • 아래 코드는 강한 결합(coupling) 상태, 교체/확장 불가능한 경직된 디자인 상태
#include <iostream>
using namespace std;

class Camera
{
public:
    void take() { cout << "take picture" << endl;}
};

class HDCamera
{
public:
    void take() { cout << "take HD picture" << endl;}
};

class People
{
public:
    void useCamera(Camera* p) { p->take(); }
    void useCamera(HDCamera* p) { p->take(); }
};

int main()
{
    People p;
    Camera c;
    p.useCamera(&c);

    HDCamera hc;
    p.useCamera(&hc);
}
  • 결합도를 낮추고 interface 개념을 활용하여 개선
    • 클래스를 바로 구현하지 않고, 규칙, 계약 등을 먼저 설계(interface) 후 상속 받아 구현
    • interface는 struct로 구현
#include <iostream>
using namespace std;


// 규칙 : 모든 카메라는 아래 클래스로부터 파생되어야 한다.
//
//       모든 카메라는 아래 인터페이스를 구현해야 한다.
struct ICamera
{
    virtual void take() = 0;
    virtual ~ICamera() {}
};

// 카메라가 없어도 카메라를 사용하는 코드를 만들수 있다.
class People
{
public:
    void useCamera( ICamera* p ) { p->take(); }
};

class Camera : public ICamera
{
public:
    void take() { cout << "take picture" << endl;}
};

class HDCamera  : public ICamera
{
public:
    void take() { cout << "take HD picture" << endl;}
};

class UHDCamera  : public ICamera
{
public:
    void take() { cout << "take UHD picture" << endl;}
};

int main()
{
    People p;
    Camera c;
    p.useCamera(&c);

    HDCamera hc;
    p.useCamera(&hc);

    UHDCamera uhc;
    p.useCamera(&uhc);
}

활용 예제

  • 모든 도형을 타입으로 설계
  • 모든 도형의 공통 기반 클래스가 있다면, 모든 도형을 하나의 컨테이너에 담아서 관리 할 수 있음
  • 모든 도형의 공통 특징은 반드시 기반 클래스에도 있어야 함
  • 파생 클래스에서 재정의 하게 되는 함수는 반드시 virtual 함수로 작성
  • 개방 폐쇄의 법칙(OCP, Open Close Principle)
    • 기능 확장에 열려있고(open), 코드 수정에는 닫혀있어야(Close)
    • 다형성은 OCP를 만족
#include <iostream>
#include <vector>
using namespace std;

class Shape
{
protected:
    // 변하는 것을 가상함수로 뽑아낸다.
    virtual void DrawImp()
    {
        cout << "Draw Shape" << endl;
    }

public:
    // final : 파생 클래스가 재정의 할수 없게 한다.
    virtual void Draw() final
    {
        cout << "mutex lock" << endl;

        DrawImp();
        cout << "mutex unlock" << endl;
    }

    virtual Shape* Clone() { return new Shape(*this);}
};

class Rect : public Shape
{
public:
    virtual void DrawImp() { cout << "Draw Rect" << endl;}
    virtual Shape* Clone() { return new Rect(*this);}
};

class Circle: public Shape
{
public:
    virtual void DrawImp() { cout << "Draw Circle" << endl;}
    virtual Shape* Clone() { return new Circle(*this);}
};

int main()
{
    vector<Shape*> v;

    while(1)
    {
        int cmd;
        cin >> cmd;

        if      ( cmd == 1 ) v.push_back(new Rect);
        else if ( cmd == 2 ) v.push_back(new Circle);
        else if ( cmd == 8 )
        {
            cout << "index >> ";
            int k;
            cin >> k;
            v.push_back( v[k]->Clone() );
        }
        else if ( cmd == 9 )
        {
            for ( auto p : v)
                p->Draw();
        }
    }
}