Design pattern in C++ - 간접층의 원리[4]
이번 시간에는 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