Design pattern in C++ - Intro[1]
디자인 패턴의 개념, 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();
}
}
}