본문 바로가기
개발/Architecture & Design Pattern

C++ - 어댑터 패턴 (Adapter Pattern)

by 피로물든딸기 2024. 2. 24.
반응형

C, C++ 전체 링크

Architecture & Design Pattern 전체 링크

 

참고

- 클래스 다이어그램 그리기

 

어댑터 패턴 (Adapter Pattern) - 구조 패턴

- 호환되지 않은 두 개의 인터페이스 사이에 중간에서 어댑터를 두어 상호 작용을 하도록 하는 패턴

- 기존의 코드나 라이브러리를 변경하지 않고 새로운 코드나 라이브러리를 통합한다.

 

구현

- 타겟 (Target) : 클라이언트는 타겟 인터페이스를 통해 어댑터에 요청을 보낸다.

- 어댑터 (Adapter) : 클라이언트가 호출하는 인터페이스를 실제로 구현하는 객체, 서비스 객체의 메서드 호출로 변환

- 어댑티 (Adaptee) : 어댑터가 클라이언트에게 제공하려는 기능을 정의하는 인터페이스

- 어댑터어댑티를 감싸서 클라이언트가 사용할 수 있는 형태로 변환한다.

- 객체 어댑터에서는 어댑터어댑티 객체를 포함한다.

- 클래스 어댑터 어댑터타겟 인터페이스를 구현하면서 어댑티 상속받는다. (타겟, 어댑티 양쪽 모두 구현)

 

장점

- SRP(단일 책임 원칙), 비즈니스 로직에서 인터페이스 또는 데이터 변환 코드를 분리할 수 있다.

OCP(개방-폐쇄 원칙), 기존 시스템을 수정하지 않고 새로운 구성 요소를 쉽게 통합할 수 있다.

- 호환되지 않는 인터페이스를 여러 번 재사용할 수 있고, 다른 클래스와 통합할 때도 사용할 수 있다.

 

단점

- 중간에 어댑터 클래스를 통해 호출을 전달하므로 오버헤드가 발생할 수 있다.

- 어댑터 클래스가 추가되고 관리되어야 하므로 복잡성이 증가한다.


Json Parser와 XML Parser

 

현재 외부 라이브러리에서 제공하는 API가 Json으로 응답을 준다고 가정하자.

class JsonParser
{
public:
	virtual ~JsonParser() = default;
	virtual string read(string rawData) = 0;
	virtual void writeJson(string json) = 0;
};

 

하지만 현재 C++ Application은 XML로 데이터를 분석하고 있다고 한다.

class XMLParser
{
public:
	virtual ~XMLParser() = default;
	virtual string read(string rawData) = 0;
	virtual void writeXML(string xml) = 0;
};

class CppXMLParser : public XMLParser
{
public:
	string read(string rawData) override
	{
		// parsing algorithm
		return "xmlData";
	}

	void writeXML(string xml) override
	{
		cout << "file write string xml -> xxx.xml" << endl;		
	}
};

 

따라서 기존 Json과 XML 코드를 변경하지 않고 서로 호환될 수 있도록 Json 어댑터가 필요하다.

// JsonAdapter는 XMLParser가 되어야 한다.
class JsonAdapter : public XMLParser { ... }

객체 어댑터 (Object Adapter)

 

Json을 XML로 변환하는 JsonAdapter와 XML을 Json으로 변환하는 XmlAdapter를 만들어보자.

 

JsonAdapt는 json data를 xml로 변환하는 XML Parser가 되어야 한다.

따라서 XML Parser(Target)인터페이스를 구현하여 json을 xml로 변환하는 알고리즘을 작성한다.

class JsonAdapter : public XMLParser
{
public:
	JsonAdapter() = default;
	JsonAdapter(JsonParser *jp) : jsonParser(jp) { }
	
	string read(string rawData) override
	{
		string json = jsonParser->read(rawData);
		string xml = /* jsonToXML */(json); // json -> xml

		xml = "json -> xmlData";

		return xml;
	}

	void writeXML(string json) override
	{
		cout << "file write string json to xml -> xxx.xml" << endl;
	}

private:
	JsonParser *jsonParser;
};

 

예시 test 코드는 다음과 같다.

void testXMLParser(XMLParser* xp)
{
	string rawData = "rawData";
	string xml = xp->read(rawData);
	cout << "xml : " << xml << endl;
	xp->writeXML(xml);
}

int main()
{
	CppXMLParser* cppXMLParser = new CppXMLParser();
	CppJsonParser* cppJsonParser = new CppJsonParser();

	JsonAdapter* jsonAdapter = new JsonAdapter(cppJsonParser);

	cout << "cppXMLParser" << endl;
	testXMLParser(cppXMLParser);

	cout << "\njsonAdapter" << endl;
	testXMLParser(jsonAdapter);
}

 

XmlAdapter도 구현한 전체 코드는 다음과 같다.

#include <iostream>
#include <string>

using namespace std;

class JsonParser
{
public:
	virtual ~JsonParser() = default;
	virtual string read(string rawData) = 0;
	virtual void writeJson(string json) = 0;
};

class CppJsonParser : public JsonParser
{
public:
	string read(string rawData) override
	{
		// parsing algorithm
		return "jsonData";
	}

	void writeJson(string json) override
	{
		cout << "file write string json -> xxx.json" << endl;
		return;
	}
};

class XMLParser
{
public:
	virtual ~XMLParser() = default;
	virtual string read(string rawData) = 0;
	virtual void writeXML(string xml) = 0;
};

class CppXMLParser : public XMLParser
{
public:
	string read(string rawData) override
	{
		// parsing algorithm
		return "xmlData";
	}

	void writeXML(string xml) override
	{
		cout << "file write string xml -> xxx.xml" << endl;		
	}
};

// XMLAdapter는 JsonParser가 되어야 한다.
class XMLAdapter : public JsonParser
{
public:
	XMLAdapter() = default;
	XMLAdapter(XMLParser *xp) : xmlParser(xp) { }

	string read(string rawData) override
	{
		string xml = xmlParser->read(rawData);
		string json = /* xmlToJson */(xml); // xml -> json 

		json = "xml -> jsonData";

		return json;
	}

	void writeJson(string xml) override
	{
		cout << "file write string xml to json -> xxx.json" << endl;
	}

private:
	XMLParser *xmlParser;
};

// JsonAdapter는 XMLParser가 되어야 한다.
class JsonAdapter : public XMLParser
{
public:
	JsonAdapter() = default;
	JsonAdapter(JsonParser *jp) : jsonParser(jp) { }
	
	string read(string rawData) override
	{
		string json = jsonParser->read(rawData);
		string xml = /* jsonToXML */(json); // json -> xml

		xml = "json -> xmlData";

		return xml;
	}

	void writeXML(string json) override
	{
		cout << "file write string json to xml -> xxx.xml" << endl;
	}

private:
	JsonParser *jsonParser;
};

void testJsonParser(JsonParser* jp)
{
	string rawData = "rawData";
	string json = jp->read(rawData);
	cout << "json : " << json << endl;
	jp->writeJson(json);
}

void testXMLParser(XMLParser* xp)
{
	string rawData = "rawData";
	string xml = xp->read(rawData);
	cout << "xml : " << xml << endl;
	xp->writeXML(xml);
}

int main()
{
	CppXMLParser* cppXMLParser = new CppXMLParser();
	CppJsonParser* cppJsonParser = new CppJsonParser();

	JsonAdapter* jsonAdapter = new JsonAdapter(cppJsonParser);
	XMLAdapter* xmlAdapter = new XMLAdapter(cppXMLParser);

	cout << "cppJsonParser" << endl;
	testJsonParser(cppJsonParser);

	cout << "\nxmlAdapter" << endl;
	testJsonParser(xmlAdapter);

	cout << "\ncppXMLParser" << endl;
	testXMLParser(cppXMLParser);

	cout << "\njsonAdapter" << endl;
	testXMLParser(jsonAdapter);
}

 

객체 어댑터클래스 다이어그램은 다음과 같다.

 

만약 Target XMLParser 인터페이스라면 Adaptee JsonParser 객체(cppJsonParser)가 된다.

이때 AdapterAdaptee를 감싸서 생성된 JsonAdapter가 된다.

 

이 과정에서 어댑터어댑티 메서드를 사용하여 요청을 변환하기 때문에

클라이언트어댑티는 서로 분리될 수 있다.

 

또한 객체 어댑터의 경우 어댑티서브 클래스에 대해서도 어댑터 역할이 가능하다.


클래스 어댑터 (Class Adapter)

 

클래스 어댑터의 경우 타겟 인터페이스를 구현하면서, 어댑티를 상속 받는다.

예를 들어 XML을 Json으로 변환하는 XMLToJsonAdapterJsonParser(타겟) 인터페이스를 구현하고,

XMLParser(어댑티)를 상속받아 메서드를 사용할 수 있다.

class XMLToJsonAdapter : public JsonParser, private CppXMLParser
{
public:
	XMLToJsonAdapter() = default;
	void writeJson(string xml) override
	{
		string json =  /* xmlToJson */(xml);
		cout << "file write string json -> xxx.json" << endl;
	}

	string read(string rawData) override
	{
		string xml = CppXMLParser::read(rawData);
		string json = /* xmlToJson */(xml); // xml -> json 

		json = "xml -> jsonData";

		return json;
	}
};

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>

using namespace std;

class JsonParser
{
public:
	virtual ~JsonParser() = default;
	virtual string read(string rawData) = 0;
	virtual void writeJson(string json) = 0;
};

class CppJsonParser : public JsonParser
{
public:
	string read(string rawData) override
	{
		// parsing algorithm
		return "jsonData";
	}

	void writeJson(string json) override
	{
		cout << "file write string json -> xxx.json" << endl;
		return;
	}
};

class XMLParser
{
public:
	virtual ~XMLParser() = default;
	virtual string read(string rawData) = 0;
	virtual void writeXML(string xml) = 0;
};

class CppXMLParser : public XMLParser
{
public:
	string read(string rawData) override
	{
		// parsing algorithm
		return "xmlData";
	}

	void writeXML(string xml) override
	{
		cout << "file write string xml -> xxx.xml" << endl;		
	}
};

class XMLToJsonAdapter : public JsonParser, private CppXMLParser
{
public:
	XMLToJsonAdapter() = default;
	void writeJson(string xml) override 
	{ 
		string json =  /* xmlToJson */(xml);
		cout << "file write string json -> xxx.json" << endl;
	}

	string read(string rawData) override
	{
		string xml = CppXMLParser::read(rawData);
		string json = /* xmlToJson */(xml); // xml -> json 

		json = "xml -> jsonData";

		return json;
	}
};

class JsonToXMLAdapter : public XMLParser, private CppJsonParser
{
public:
	JsonToXMLAdapter() = default;
	void writeXML(string json) override
	{
		string xml =  /* jsonToXML */(json);
		cout << "file write string xml -> xxx.xml" << endl;
	}

	string read(string rawData) override
	{
		string json = CppJsonParser::read(rawData);
		string xml = /* jsonToXML */(json); // json -> xml

		xml = "json -> xmlData";

		return xml;
	}
};

void testJsonParser(JsonParser* jp)
{
	string rawData = "rawData";
	string json = jp->read(rawData);
	cout << "json : " << json << endl;
	jp->writeJson(json);
}

void testXMLParser(XMLParser* xp)
{
	string rawData = "rawData";
	string xml = xp->read(rawData);
	cout << "xml : " << xml << endl;
	xp->writeXML(xml);
}

int main()
{
	CppJsonParser* cppJsonParser = new CppJsonParser();
	XMLToJsonAdapter* xml2JsonParser = new XMLToJsonAdapter();

	cout << "cppJsonParser" << endl;
	testJsonParser(cppJsonParser);

	cout << "\nxml2JsonParser" << endl;
	testJsonParser(xml2JsonParser);

	CppXMLParser* cppXMLParser = new CppXMLParser();
	JsonToXMLAdapter* json2XMLParser = new JsonToXMLAdapter();

	cout << "\ncppXMLParser" << endl;
	testXMLParser(cppXMLParser);

	cout << "\njson2XMLParser" << endl;
	testXMLParser(json2XMLParser);

	return 0;
}

 

클래스 어댑터의 클래스 다이어그램은 다음과 같다.

 

클래스 어댑터는 상속을 사용하므로, 어댑티의 메서드를 오버라이드 할 수 있고, 어댑티 전체를 다시 구현할 필요가 없다.


ClassDiagram.md

```mermaid
  classDiagram    
    class Target {
      operation()*
    }    
    class Adapter {
      operation()
    }    
    class Adaptee {
      adapteeOperation()
    }    
    
  
    <<interface>> Target
    Client --> Target    
    Adapter ..|> Target
    Adapter --> Adaptee
```
```mermaid
  classDiagram    
    class Target {
      operation()
    }    
    class Adapter {
      operation()
    }    
    class Adaptee {
      adapteeOperation()
    }    
    
  
    <<interface>> Target
    Client --> Target    
    Adapter ..|> Target
    Adapter --|> Adaptee
```
반응형

댓글