이번 시간에는 Adapter, Proxy, Facade, Bridge에 대해 알아보겠습니다.

Adapter 패턴

정의

  • 한 객체의 인터페이스를 다른 클래스가 새로운 이름으로 정의하여 wrapping하여 사용
  • 클래스 어답터
    • 클래스의 인터페이스를 변경
    • 다중 상속을 이용하거나 값으로 포함 관계 형성
    • 이미 존재하던 객체의 인터페이스 변경 불가
  • 객체 어답터
    • 객체의 인터페이스를 변경
    • Composition 패턴 활용
    • 객체 포함관계 형성시 포인터 or 참조로 포함

예제

  • Share 타입으로 어답터와 TextView를 담을 수 있음
#include <iostream>
#include <vector>
#include "TextView.h"
using namespace std;

class Shape
{
public:
    virtual void Draw() { cout << "Draw Shape" << endl;}
};

// TextView 클래스의 인터페이스를 도형편집기에 맞도록 수정
// 클래스 어답터.
class Text : public TextView, public Shape
{
public:
    Text( string s) : TextView(s) {}

    virtual void Draw() { TextView::Show(); }
};

// 객체 어답터.
class ObjectAdapter :  public Shape
{
    TextView* pView; // 포인터가 핵심
public:
    ObjectAdapter( TextView* p) : pView(p) {}

    virtual void Draw() { pView->Show(); }
};

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

    TextView tv("world"); // 이미 존재 하던 객체

    v.push_back( new ObjectAdapter(&tv)); // ObjectAdapter를 이용하여 tv를 넣을 수 있음
    v.push_back( new Text("hello"));

    for ( auto p : v)
        p->Draw();
}

STL에서 활용하고 있는 Adapter 패턴

  • std::list를 활용하여 std::stack을 구현함
    • sequence container를 활용하여 stack, qeue, priority_queue를 제공(클래스 어답터)
#include <iostream>
#include <list>
#include <vector>
#include <deque>
#include <stack>
using namespace std;

// list의 함수이름을 stack 처럼 보이도록 변경
/*
template<typename T> class Stack : private list<T>
{
public:
    void push(const T& a) { list<T>::push_back(a);}
    void pop()            { list<T>::pop_back();}
    T&   top()            { return list<T>::back();}
};
*/
/*
template<typename T, typename C = deque<T> > class Stack
{
    C st;
public:
    inline void push(const T& a) { st.push_back(a);}
    void pop()            { st.pop_back();}
    T&   top()            { return st.back();}
};
*/

int main()
{
    //Stack<int, list<int> > s;
    //Stack<int, vector<int> > s;
    stack<int> s;
    s.push(10);
    s.push(20);

//    s.push_front(20);

    cout << s.top() << endl; // 20
}
  • iterator 에서 활용
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;

int main()
{
    list<int> s = { 1,2,3,4};

    auto p1 = s.begin();
    auto p2 = s.end();

    reverse_iterator< list<int>::iterator > p4(p1); // --p1 로 초기화

    auto p3 = make_reverse_iterator(p2);

    for_each( p3, p4, [](int a) { cout << a << endl;});
}

Proxy 패턴

정의

  • 어떤 객체에 대한 접근을 제어하기 위한 용도로 delegate에 해당하는 객체를 제공하는 패턴

  • SDK 형태로 제공하는 것과 유사

  • 장점

    • 명령 코드 대신, 함수 호출 사용

    • Client 코드는 connection, send 및 기타 예외 처리에 대해 알 필요가 없다.

    • 보안의 기능을 추가하거나, 자주 사용되는 요청에 대한 캐싱 처리를 할 수 있다.

예제

  • 프로세스간 통신을 하는 프로그램에서 서버에 직접 명령을 send 하는 것이 아닌, 객체를 통해 여러 작업 수행
#include <iostream>
using namespace std;
using namespace ecourse;

// Proxy 객체
class Calc
{
    int server;
public:
    Calc() { server = find_server("CalcService");    }

    int Add(int a, int b) { return send_server(server, 1, a, b);}
    int Sub(int a, int b) { return send_server(server, 2, a, b);}
};

int main()
{
    Calc* pCalc = new Calc;

	cout << pCalc->Add(1, 2) << endl;
    cout << pCalc->Sub(10, 8) << endl;
}

Proxy 패턴의 종류

  • Remote proxy : 원격 객체에 대한 접근 제어
  • Virutal proxy : 생성하기 힘든 자원에 대한 접근
  • Protected proxy : 접근 권한이 필요한 자원에 대한 접근 제어

Facade 패턴

정의

  • 서브 시스템을 합성하는 다수의 객체들의 인터페이스 집합에 대해 일관된 하나의 인터페이스를 제공
  • 파사드는 서브시스템을 사용하기 쉽게 하기 위한 포괄적 개념의 인터페이스를 제공

예제

  • socket 관련 win API를 모듈화 하여 사용하기 쉽게 만든 클래스
#include <iostream>
#include <WinSock2.h>
#include <string>
using namespace std;

// network 라이브러리의 초기화와 cleanup을 담당.
class NetworkInit
{
public:
	NetworkInit()
	{
		WSADATA w;
		WSAStartup(0x202, &w);
	}
	~NetworkInit()
	{
		WSACleanup();
	}
};

// IP 주소 관리.
class IPAddress
{
	struct sockaddr_in addr;
public:
	IPAddress(const char* ip, short port)
	{
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port);
		addr.sin_addr.s_addr = inet_addr(ip);
	}
	struct sockaddr* getRawAddress() { return (struct sockaddr*)&addr; }
};

// socket 프로그래밍의 일반적인 절차.
class Socket
{
	int sock;
public:
	Socket(int type) { sock = socket(PF_INET, type, 0); }

	void Bind(IPAddress* ip)
	{
		::bind(sock, ip->getRawAddress(), sizeof(struct sockaddr_in));
	}
	void Listen() { ::listen(sock, 5); }
	void Accept()
	{
		struct sockaddr_in addr2 = { 0 };
		int sz = sizeof(addr2);
		accept(sock, (struct sockaddr*)&addr2, &sz);
	}
};

class TCPServer
{
    NetworkInit init;
    Socket sock;
public:
    TCPServer() : sock(SOCK_STREAM) {}

    void Start(const char* sip, short port)
    {
        IPAddress ip(sip, port);
    	sock.Bind(&ip);
    	sock.Listen();
    	sock.Accept();
    }
};

int main()
{
	TCPServer server;
    server.Start( "127.0.0.1", 4000);
}

Bridge 패턴

정의

  • 구현의 추상화 개념을 분리해서 각각을 독립적으로 변형 할 수 있게함
  • 최상위 인터페이스로 부터 파생된 클래스가 많을 경우, 중간 인터페이스 단계를 둬서 파생 클래스들의 수정을 최소화 함
#include <iostream>
using namespace std;

struct IMP3
{
    virtual void Play() = 0;
    virtual void Stop() = 0;
    virtual ~IMP3() {}
};

class IPod :  public IMP3
{
public:
    void Play() { cout << "Play MP3" << endl;}
    void Stop() { cout << "Stop MP3" << endl;}
};
//---------------------------
class MP3
{
    IMP3* pImpl;
public:
    MP3()
    {
        pImpl = new IPod;
    }
    void Play() { pImpl->Play();}
    void Stop() { pImpl->Stop();}
    void PlayOneMinute()
    {
        pImpl->Play();
        Sleep(1);
        pImpl->Stop();
    }
};

class People
{
public:
    void UseMP3(MP3* p )
    {
        p->Play();
        p->PlayOneMinute();
    }
};

int main()
{

}

include가 많은 파일을 사용 할 때 컴파일 속도 개선하기(PIMPL : Pointer to IMPlementation)

  • .h에서 Implement 클래스를 포인터 전방 선언만 한 후 구현 파일을 따로 분리
#include "PointImpl.h"
#include "Point2.h"

Point::Point(int a, int b)
{
    pImpl = new PointImpl(a, b);
}

void Point::Print() const
{
    pImpl->Print();
}

// PointImpl.cpp
#include <iostream>
#include "PointImpl.h"
using namespace std;

PointImpl::PointImpl( int a, int b) : x(a), y(b) {}

void PointImpl::Print() const
{
    cout << x << ", " << y << endl;
}

정리

1. 해결하기 어려워 보이는 문제도 중간층을 도입 하면 해결 할 수 있다.

2. 왜 중간층을 사용하는지? -> “의도"가 중요

  • 기존 인터페이스의 변경 -> Adapter
  • 대리 처리 -> Proxy
  • 서브 시스템의 복잡한 과정을 감추기 위함 -> Facade
  • Update를 독립적으로 수행 -> Bridge