간접층의 원리

EHminShoov2J·2024년 10월 10일
0

Design Pattern

목록 보기
4/6
post-thumbnail

1. Adapter

1.1. 의도

  • 인터페이스를 클라이언트가 기대하는 형태의 인터페이스로 변환

1.2. 구현 방법

  • Class Adapter를 활용한 구현
    • 다중 상속을 활용하여 CoolText의 show()함수를 draw()함수로 탈바꿈
    • Object Adapter를 사용하는 경우에 비하여 가상함수를 통해 재정의 할 가능성이 남아 있음.
 class ClsAdapter : public CoolText, public Shape 
{
public:
	ClsAdapter(const std::string& text) 
			: CoolText(text) {}
	void draw() override { CoolText::show();}
};	
  • Object Adapter를 활용한 구현
    • 이미 생성되어 있는 객체를 포인터 혹은 Reference 타입으로 불러와서 해당 객체의 함수를 호출
class ObjAdapter : public Shape  // 객체 어댑터
{
	CoolText* ct; // 객체를 받아올 떄는 보통 이런 형식을 많이 사용한다.
public:
	ObjAdapter(CoolText* ct) : ct(ct) {}
	void draw() override { ct->show();}
};	

1.3. STL 에서의 활용

  • stack을 구현하고자 할때 이미 다른 클래스에서 잘 만들어진 메모리 관리, 혹은 기본 기능들을 굳이 재정의하여 코드 메모리를 늘리고 싶지 않음
  • object adapter를 활용하여 stack을 만들고, policy based design을 통해 사용자에게 선택권을 제공
template<typename T, typename C = std::deque<T> > 
class stack 
{
	C c;
//	C* c; // object adapter
public:
	constexpr void push(const T& a) { c.push_back(a); }
	constexpr void pop()		    { c.pop_back(); 	}
	constexpr T&   top()            { return c.back(); }
};
#include <stack>
int main()
{
	stack<int, std::vector<int>> s1; // cash 사용에 따라 유리할 수 있음
	stack<int, std::list<int>> s2;
	stack<int> s;
	s.push(10); // 인라인화가 이루어진다면 s.c.push_back(a) 가 되어 마치 stack은 없는 것처럼 작동하게 된다.
	s.push(20); // 기계어 코드에는 stack과 관련된 코드들은 없는 것 처럼 받아들여진다.
}

1.4. private을 이용한 상속

  • 구현은 물려받았지만 인터페이스는 물려받지 않았다
  • 상속을 모두 private 영역 안에 받게 하여, 외부에 노출하고 싶지 않은 부모 클래스의 기능을 숨길 수 있음. (java에서의 stack이 vector로부터 일반 상속을 받아 구현되어 push_front와 같은 함수가 ide에 잡히는 문제 발생)

2. Proxy

2.1. 의도

  • 다른 객체에 접근하기 위한 중간 대리 역할을 하는 객체를 생성, 대리 역할하는 객체에서 다양한 문제를 해결(인증, 보안, 원격지 서버 대행)

2.2 예제

  • Image 객체를 호출하면 Image가 로드됨.
  • 하지만 주로 하고 싶은 작업은 이미지의 크기를 불러오는 것 >> 불필요하게 메모리가 소모됨.
  • ImageProxy 클래스를 생성하여 문제 해결(헤더파일만을 읽는 방식으로 진행)
  • 이미지를 그려야 할때 Image 객체를 생성해서 그림을 그림 ( 지연된 생성 )
class Image : public IImage
{
	std::string name;
public:
	Image(const std::string& name) : name(name)
	{
		std::cout << "open " << name << '\n';
	}
	void draw() { std::cout << "draw " << name << '\n'; }

	int width()  const { return 100;}
	int height() const { return 100;}
};

class ImageProxy : public IImage
{
	std::string name;
	Image* img = nullptr;
public:
	ImageProxy(const std::string& name) : name(name) {}

	int width()  const { return 100;} // 파일헤더에서 정보 획득
	int height() const { return 100;} // 파일헤더에서 정보 획득
	void draw() 
	{
		if ( img == nullptr )
			img = new Image(name); // 꼭 그림을 그려 주어야 할때는 img 객체를 생성해서 진행
			// 지연된 생성이라 부름 
		img->draw();
	}
};

2.3. 차별점

  • 브릿지와 유사하지만, 브릿지는 상호간의 업데이트를 위한 패턴
  • 데코레이터와 유사하게 기능을 추가하는 것 같지만, 데코레이터는 중첩의 형태로 진행되며, 인터페이스가 동일함 / 반면에 프록시는 중첩이 안되며 지연된 생성을 활용한다는 점이 다름

3. Facade Pattern

3.1. 의도

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

3.2. 예제

  • 내부적인 코드를 모두 감싸 TCP server에 대해서 몰라도 쉽고 간편하게 사용할 수 있도록 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);
}

4. Bridge

4.1 의도

  • 구현과 추상화 개념을 분리하여 각각 독립적으로 변형할 수 있게 한다.
  • 클라이언트에서 어떤 추가적인 기능을 원할때 인터페이스가 바뀌면 모든 구현 클래스들이 변경되어야 함.
  • Abstraction 이라는 가상의 클래스를 통해 간단한 기능은 해당 클래스에서 구현할 수 있고, 구현계층이 변경되더라도 사용자와는 아무 상관이 없다.

4.2. 예제

  • 업데이트를 편안하게 하고 싶다면 중간에 계층을 늘려 상호 독립적으로 업데이트! (단 성능 저하가 발생할 수 있음)
class MP3
{
	IMP3* impl; //P - IMPL point to implementation으로 불리기도 함 .
public:
	MP3(IMP3* p = nullptr) : impl(p)
	{
		if (impl == nullptr)
			impl = new IPod;
	}

	void play() { impl->play(); }
	void stop() { impl->stop(); }

	void play_one_minute()
	{
		impl->play();
		// 1분 후에.. 
		impl->stop();
	}
};

4.3. PIMPL

  • Pointer to Implementation 으로 cpp 에서는 유용하나 다른 언어에서는 적합하지 않을 수 있다.
  • Point.h가 간접 계층이 되어준다면 PointImpl이 변경되어도 main은 Point.h만을 사용하기 때문에 변경될 것이 없다 (== maind을 전부 재 컴파일 할 필요가 없다.)
  • Pointer를 선언할때 include 없이 전방선언으로도 가능하기 때문에 가능한 기법이다.
  • 컴파일러 방화벽이자 완벽한 정보 은닉이 가능해진다.(실제 구현 파트는 DLL로 제공)
//#include "PointImpl.h"
class PointImpl; // 핵심.. 포인터를 쓸때는 전방선언으로도 충분하다.

class Point
{
	PointImpl* impl;
public:
	Point(int x, int y);

	void print() const;
};

0개의 댓글