간접층을 만들어 유연성과 확장성을 확보하기 위한 대표적인 기법들을 소개한다.

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();
};