On this page
article
Indirection: Adapter, Proxy, Facade, Bridge
간접층을 만들어 유연성과 확장성을 확보하기 위한 대표적인 기법들을 소개한다.
Adapter
어댑터는 기존 클래스의 인터페이스를 클라이언트가 기대하는 인터페이스로 바꿔주는 중간 계층이다. 즉, 호환되지 않는 인터페이스를 연결해주는 역할을 한다.
- 객체 어댑터: 기존 객체를 포함(composition)해서 위임 호출
- 클래스 어댑터: 다중 상속을 이용해 기존 클래스와 클라이언트 인터페이스를 동시에 상속
// 객체 어댑터 예시
class Shape {
public:
virtual void draw() = 0;
};
class LegacyRect {
public:
void draw_old() { std::cout << "draw old rect"; }
};
class RectAdapter : public Shape {
LegacyRect* p;
public:
RectAdapter(LegacyRect* p) : p(p) {}
void draw() override { p->draw_old(); }
};
STL에서의 어댑터
std::stack
,std::queue
등은 내부적으로deque
,vector
를 감싸서 제공하는 어댑터- 반복자 어댑터:
reverse_iterator
등은 기존 반복자에 동작을 바꿔서 사용
Proxy
프록시는 실제 객체에 접근하기 전에 그 접근을 조절하거나, 부가 기능을 추가할 수 있는 객체다.
예: 이미지 로딩을 미뤄두는 경우, 실제 이미지 대신 이미지 프록시를 사용
class IImage {
public:
virtual void draw() = 0;
};
class RealImage : public IImage {
std::string filename;
public:
RealImage(const std::string& fn) : filename(fn) { load(); }
void load() { std::cout << "Loading..." << filename << std::endl; }
void draw() override { std::cout << "Drawing..." << filename << std::endl; }
};
class ImageProxy : public IImage {
RealImage* real = nullptr;
std::string filename;
public:
ImageProxy(std::string fn) : filename(fn) {}
void draw() override {
if (!real)
// load & show other thing
real->draw();
}
};
Facade
파사드는 복잡한 서브시스템을 간단한 인터페이스로 감싸주는 역할을 한다. 주로 라이브러리나 서브시스템의 초기화, 종료, 처리 순서를 감춘다.
class TCPServer {
NetworkInit init;
Socket sock{SOCK_STREAM};
public:
void Start(const char* ip, short port) {
IPAddress addr(ip, port);
sock.Bind(&addr);
sock.Listen();
sock.Accept();
}
};
int main() {
TCPServer server;
server.Start("127.0.0.1", 4000);
}
Bridge
브릿지는 구현부와 추상부를 분리해서 독립적으로 변화 가능하게 만드는 구조다. 객체를 포함해서 호출을 위임하고, 실제 구현은 바깥에서 주입받는다.
아래와 같은 예시에서 People은 IMP3를 통해서 MyMp3에 연결되고, 이 MyMp3에서 실제 구현을 주입받음.
추상 클래스가 Bridge가 되는 셈.
class IMP3 {
public:
virtual void play() = 0;
virtual void stop() = 0;
};
class MyMP3 : public IMP3 {
public:
void play() override { std::cout << "Play"; }
void stop() override { std::cout << "Stop"; }
};
class People {
IMP3* mp3;
public:
People(IMP3* p) : mp3(p) {}
void preview() {
mp3->play();
// 1분 미리듣기 후
mp3->stop();
}
};
PIMPL (Bridge 변형)
- 구현 세부를 header에 노출하지 않기 위한 idiom
- 실제 구현은
.cpp
에 감추고, header에는 포인터만 forward 선언 - 장점: 의존성 최소화, 컴파일 시간 단축
// Point.h
class PointImpl;
class Point {
PointImpl* pImpl;
public:
void draw();
};