[Design pattern] 2-1. 어댑터 패턴(Adapter pattern)


Study/Design Pattern  2021. 10. 26. 17:02

안녕하세요. 명월입니다.


이 글은 디자인 패턴의 어댑터 패턴(Adapter pattern)에 대한 글입니다.


어댑터 패턴부터는 구조 패턴입니다. 구조 패턴이란 여러 클래스나 객체를 조합하여 더 큰 구조를 만드는 패턴입니다. 이전 생성 패턴에서는 new를 이용한 인스턴스를 생성하는 형태가 중심이었다면 구조 패턴에서는 클래스나 객체의 구조를 어떻게 구성하는 것에 더 중점을 두는 패턴입니다.

어댑터 패턴은 인터페이스로 묶이지 않은 다른 클래스를 형태를 같은 인터페이스의 형태로 변환하는 것이 목표입니다.

출처 - https://en.wikipedia.org/wiki/Adapter_pattern

#pragma once
#include <stdio.h>
#include <iostream>
#include <vector>
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual string getData() = 0;
  // 소멸자 추상화
  virtual ~INode() { }
};
// INode 인터페이스를 상속 받는다.
class Node1 : public INode {
public:
  // getData 함수 재정의
  virtual string getData() {
    // String 데이터를 return 한다.
    return "Node1 Class - getData()";
  }
};
// Node2 클래스, INode 인터페이스를 상속 받지 않았다.
class Node2 {
public:
  // getData 함수
  string getData() {
    // String 데이터를 return 한다.
    return "Node2 Class - getData()";
  }
};
// Node2 클래스의 어댑터 패턴 (Node2가 INode 인터페이스 안에서 사용할 수 있게 한다.)
class Node2Adapder : public INode {
private:
  // 맴버 변수
  Node2* node;
public:
  // 생성자, Node2의 인스턴스를 받는다.
  Node2Adapder(Node2* node) {
    // 맴버 변수 설정
    this->node = node;
  }
  // 소멸자
  ~Node2Adapder() {
    // 맴버 변수 Node2를 해제한다.
    delete this->node;
  }
  // getData 함수 재정의
  virtual string getData() {
    // Node2 인스턴스의 getData를 호출
    return this->node->getData();
  }
};
// 실행 함수
int main() {
  // 백터 선언 (INode 인터페이스를 상속 받은 인스턴스만)
  vector<INode*> v;
  // Node1은 INode를 상속 받았으므로 OK
  v.push_back(new Node1());
  // Node2는 INode를 상속받지 않았지만 Adapter 패턴으로 INode 인터페이스에 포함하게 한다.
  v.push_back(new Node2Adapder(new Node2()));
  // 반복문으로 INode 인스턴스 취득
  for (INode* n : v) {
    // getData 데이터를 호출하고 콘솔에 출력
    cout << n->getData() << endl;
    // 메모리 해재
    delete n;
  }

  return 0;
}

위 예제를 보면 제가 main 함수에서 INode 인터페이스의 타입의 객체들을 vector를 통해 관리하기 위해 선언했습니다.

그런데 Node2 클래스는 INode 인터페이스를 상속 받은 받은 클래스가 아니여서 INode 인터페이스 그룹으로 묶을 수가 없습니다. 클래스의 구조는 비슷한데 말입니다.

Node2를 INode에 상속받게 하면 아주 간단하게 될지도 해결될 지도 모르겠습니다만, 상황에 따라서 Node2클래스를 수정하면 안된다고 한다면 위처럼 Adapter 클래스를 만들어서 Node2 클래스를 마치 INode에 상속 받은 것처럼 만들 수 있습니다.

import java.util.ArrayList;
// INode 인터페이스
interface INode {
  // 추상 함수
  void print();
}
// INodeAnother 인터페이스
interface INodeAnother {
  // 추상 함수
  String output();
}
// INode 인터페이스를 상속받은 Node1 클래스
class Node1 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println("Node1 - print()");
  }
}
// INodeAnother 인터페이스를 상속받은 Node2 클래스
class Node2 implements INodeAnother {
  // 함수 재정의
  public String output() {
    // String 값을 리턴
    return "Node2 - output()";
  }
}
// INodeAnother 인터페이스의 adapter 패턴, INode 인터페이스를 상속
class INodeAnotherAdapter implements INode {
  // 맴버 변수
  private INodeAnother node;
  // 생성자
  public INodeAnotherAdapter(INodeAnother node) {
    this.node = node;
  }
  // 함수 재정의
  public void print() {
    // INodeAnother 인터페이스의 output 함수의 값을 받아 출력
    System.out.println(node.output());
  }
}
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // INode의 리스트 생성
    var list = new ArrayList<INode>();
    // Node1 인스턴스 생성
    list.add(new Node1());
    // Node2 인스턴스를 INodeAnotherAdapter 어댑터를 통해 변환
    list.add(new INodeAnotherAdapter(new Node2()));
    // 반복문 추출
    for (var node : list) {
      // print 함수 출력
      node.print();
    }
  }
}

Adapter 패턴이 꼭 클래스만 적용하라는 법은 없습니다. 위처럼 Interface 어댑터를 만들어서 INodeAnother를 상속받은 클래스는 INode 인터페이스를 상속받은 어댑터 클래스로 변환하는 게 가능합니다.

using System;
using System.Collections.Generic;

// INode 인터페이스
public interface INode
{
  // 추상 함수
  void Print();
}
// Node2 클래스
public class Node2
{
  // String 값을 리턴하는 함수
  public string Output()
  {
    return "Node2 - Output()";
  }
}
// INode 인터페이스를 상속받은 Node 클래스
public class Node1 : INode
{
  // 출력 함수
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine("Node1 - Print()");
  }
}
// 이번엔 상속을 통해 Adapter를 구현헀다.
public class Adapter : Node2, INode
{
  // 함수 재정의
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine(base.Output());
  }
}
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // INode의 리스트 생성
    var list = new List<INode>();
    // Node1 클래스의 인스턴스 생성
    list.Add(new Node1());
    // Node2 클래스를 상속받은 Adapter 클래스를 통해 Adapter 패턴을 구현
    list.Add(new Adapter());
    // 반복문을 통해 추출
    foreach (var node in list)
    {
      // 함수 호출
      node.Print();
    }
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

어댑터 패턴은 기본적으로 생성자에서 형을 변환할 클래스를 받아서 새로운 클래스로 감싸는 것을 말합니다.

그런데 꼭 생성자에서 받을 필요없이 위처럼 상속을 통해서도 어댑터 패턴을 구현할 수 있습니다.


여기까지 디자인 패턴의 어댑터 패턴(Adapter pattern)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.