구조 패턴은 더 큰 구조를 형성하기 위해 어떻게 클래스와 객체를 합성하는가에 주목하는 패턴입니다.

 

적응자 패턴은 객체를 특수한 목적으로 사용가능하도록 적응(변환)시키는 디자인 패턴입니다.

해당 패턴은 주로 이미 개발이 완료된 서비스에 새로운 기능을 도입하려는 경우 새로운 기능과 기존의 기능의 인터페이스가 상이할 경우 활용할 수 있습니다.

 

1️⃣ 동기

그림판 툴을 예시로 들어보겠습니다.

프로그램은 Shape라는 클래스를 만들어 해당 클래스의 서브 클래스들이 드래그를 통해 위치를 옮기고 회전할 수 있는 기능을 제공합니다.

원, 삼각형 과 같은 그래픽 객체를 추가하는 것은 비교적 간단했지만 텍스트 객체를 추가하는 경우 전혀다른 접근이 필요합니다.

개발자는 기존의 TextView객체를 활용하여 해당 문제를 해결하고 싶었지만, TextView는 해당 목적으로 만들어진 타입이 아니라 사용할 수 없습니다. (Shape를 상속할 목적으로 생성되지 않음)

이 경우 적응자 패턴을 활용하여 Shape와 TextView사이 중간 매개자를 만들어 문제를 해결할 수 있습니다.

 

2️⃣ 활용성

  • 기존 클래스를 특수한 목적으로 활용하고 싶지만 인터페이스가 맞지 않을 때

 

3️⃣ 구조

어뎁터 패턴은 크게 2가지 구조로 구현될 수 있습니다.

  1. 다중상속을 지원하는 언어의 경우 Shape와 TextView를 모두 상속하는 클래스를 만들어 두 클래스를 매개시킬 수 있습니다.
  2. Shape를 상속하는 어뎁터에 적응 대상(TextView)을 합성하여 매개시키는 방식입니다.

 

4️⃣ 패턴 참여자

패턴참여자 관계도

  • 타겟: 클라이언트가 사용할 인터페이스를 정의한 클래스입니다. 앞서언급한 예제중 Shape클래스가 해당 역할에 해당합니다.
  • 클라이언트: 프로그램에서 타겟 인터페이스를 활용하는 객체입니다.
  • 적응대상자(Adaptee): 타겟 인터페이스로 적응이 필요한 객체입니다. TextView가 이에 해당합니다.
  • 적응자(Adapter): 적응대상자를 클라이언트가 사용할 수 있도록 타겟 인터페이스로 변환하는 역할을 수행합니다.
    • 해당 작업을 적응시킨다, 랩핑한다라고 표현합니다.

 

5️⃣ 패턴 참여자 협력 방법

클라이언트는 어뎁터에게 메세지를 보내고 어뎁터는 적응대상자의 인터페이스를 사용하여 적절하게 응답합니다.

 

6️⃣ Swift구현

타겟을 구현합니다. Swift의 프로토콜을 활용하였습니다.

class Target {
    func request() -> String { fatalError() }
}

 

기존의 클래인 FileFetcher를 Target인터페이스로 변환하고 싶은 경우 어뎁터를 통해 가능합니다.

// 기존 클래스 (호환되지 않음)
class FileFetcher {
    func specificRequest() -> String {
        return "file"
    }
}

// Adapter 클래스: Target 인터페이스를 만족시키면서 내부에서 Adaptee를 사용
class FileSystemAdapter: Target {
    private let adaptee = FileFetcher()
    
    override func request() -> String {
        return adaptee.specificRequest()
    }
}

 

어뎁터를 통해 기존의 기능을 활용한 예제입니다.

let target1: Target = FileSystemAdapter()
print(target1.request()) // file

 

 

위임자 패턴을 활용하여 구현한 예제입니다.

protocol TargetDelegate {
    func create() -> String
}

class Target2 {
    private var delegate: TargetDelegate
    
    init(delegate: TargetDelegate) {
        self.delegate = delegate
    }
    
    func request() {
        let str = delegate.create()
    }
}

 

위임자 프로토콜을 어뎁터가 구체화하고 생성자로 전달합니다.

class CoreDataAdapter: TargetDelegate {
    
    private var adaptee = CoreDataService()
    
    func create() -> String {
        return adaptee.read()
    }
}

class CoreDataService {
    func read() -> String {
        "CoreData"
    }
}

let target2 = Target2(delegate: CoreDataAdapter())
print(target2.request())

 

 

 

 

7️⃣ 느낀점

Swift언어의 경우 프로토콜을 다중 채택할 수 있기 때문에 사실 어뎁터패턴이 필요했던 문제를 쉽게 해결할 수 있다고 생각합니다.

어뎁터를 사용하지 않고 기존 타입에 익스텐션+프로토콜을 활용하는 방식으로도 문제를 해결할 수 있을 것이라고 판단됩니다.

하지만 슈퍼 클래스 기반의 협력의 경우 어뎁터 패턴을 사용해야만 하는 경우들이 존재합니다.

예를들어 UITableView의 Cell은 반드시 UITableViewCell클래스의 하위타입이어야 합니다.

기존의 UIView를 활용하고 싶은 경우 해당 뷰를 UITableViewCell클래스의 하위타입으로 랩핑할 필요가 있습니다.

해당 협력관계에서 UITableViewCell이 어뎁터 역할을 수행하고 기존의 View가 적응대상자 역할을 수행한다고 볼 수 있습니다.

 

 

구현 코드는 아래 저장소에서 확인할 수 있습니다.

 

GitHub - J0onYEong/GOF-design-pattern: GOF디자인 패턴 실습코드 레포지토리입니다.

GOF디자인 패턴 실습코드 레포지토리입니다. Contribute to J0onYEong/GOF-design-pattern development by creating an account on GitHub.

github.com