jane1choi/TIL

[Design Pattern] 구조 패턴 - Adapter Pattern

Closed this issue · 0 comments

구조 패턴

어댑터 패턴은 디자인패턴 중 구조 패턴에 해당하기 때문에, 구조 패턴이 무엇인지 부터 알아보겠습니다!
구조 패턴은 클래스와 객체가 어떻게 구성되는지와 관련이 있습니다.
구조 패턴은 새로운 기능을 구현하기 위해 인터페이스나 구현으로 처리하기 보다는 객체를 구성하는 방법을 정의합니다. 이렇게 추가된 기능들의 유연성은 런타임에서 구성을 변경할 수 있는 능력에서 비롯되며, 정적 클래스로는 할 수 없는 일입니다.

간단하게 말해서 구조 패턴은 여러개의 독립적인 클래스를 하나처럼 사용할 때 사용하거나 여러 인터페이스를 통합하여 하나의 인터페이스로 만드는 패턴이라고 할 수 있습니다.

Adapter Pattern (어댑터 패턴)

어댑터 패턴은 호환되지 않는 인터페이스를 가진 두 개의 객체가 함께 동작할 수 있도록 해주는 구조 설계 패턴입니다.
특정 객체의 인터페이스를 변환해서 다른 객체에 적응시켜 사용할 수 있게 도와주어,
인터페이스가 서로 다른 타입이 동일한 형식으로 작동할 수 있도록 만들어 줍니다.

해외여행가면 우리나라에서 쓰는 220V용 콘센트가 맞지 않는 나라들도 많아서 어댑터 챙겨가잖아요??
해당 나라의 규격에 맞지 않는 콘센트를 어댑터를 연결해 사용할 수 있게 되는 것 처럼,
서로 다른 인터페이스를 가진 객체를 어댑터 패턴이 함께 사용할 수 있도록 변환해주는 역할을 한다고 이해하면 쉬울 것 같습니다.

Adapter 패턴을 UML로 도식화하면 아래와 같습니다.
R1280x0-4

  • Client : 외부 라이브러리나 시스템을 사용하려는 객체
  • Adaptee : 외부 라이브러리 및 외부 시스템 객체(기존 객체) → 인터페이스가 다르기 때문에 바로 사용할 수 없다.
  • Target : Adapter가 구현하는 추상 인터페이스 객체, 클라이언트는 타겟(Target)의 인터페이스를 통해서 Adaptee의 인터페이스를 사용하게 된다. (다른 클래스가 현재 로직에 포함되려면 따라야 하는 프로토콜)
  • Adapter : Adaptee(기존 객체)와 클라이언트 둘 다 사용할 수 있도록 중간에서 연결시켜주는 역할을 한다. 타겟(Target) 인터페이스를 구현하며 클라이언트는 타겟 인터페이스를 통해 어댑터에 요청을 보낸다. Adapter는 Client, Adaptee 객체를 모두 다룰 수 있고 Adaptee 객체를 Client 인터페이스로 래핑 하거나 Client 객체를 Adaptee 클래스가 사용할 수 있는 형식으로 변환한다.
    → Adapter를 사용하면 기존 Client 코드를 수정하지 않고도 새로운 클래스들을 사용할 수 있습니다.

아까 콘센트로 예시를 들었는데 요렇게 잘 비유해 놓은 그림이 있더라구요??
R1280x0-5

예시코드

쉬운 이해를 위해 간단한 예시 코드를 보겠습니다.

/// Adapter가 구현하는 추상 인터페이스 객체.
/// 클라이언트는 Target의 인터페이스를 통해서 Adaptee의 인터페이스를 사용하게 된다.
class Target {
    func request() -> String {
        return ""
    }
}

// 외부 라이브러리 및 외부 시스템 객체(기존 객체)
class Adaptee {
    public func specificRequest() -> String {
        return "Adaptee's specificRequest."
    }
}

// 인터페이스와 호환되도록 해주는 어댑터
// 타겟(Target) 인터페이스를 구현하며 클라이언트는 타겟 인터페이스를 통해 어댑터에 요청을 보낸다.
class Adapter: Target {
    private var adaptee: Adaptee

    init(_ adaptee: Adaptee) { // 의존성 주입
        self.adaptee = adaptee
    }

    override func request() -> String { // 추상 인터페이스 객체 상속받아서 함수 재정의
        return self.adaptee.specificRequest()    }
}

// 외부 라이브러리나 시스템을 사용하려는 객체
class Client {
    static func specificRequest(target: Target) {
        print("Call from adapter - \(target.request())")
    }
}

let adaptee = Adaptee()

print(adaptee.specificRequest()) 
// Adaptee's specificRequest.
        
Client.specificRequest(target: Adapter(adaptee)) 
// Call from adapter - Adaptee's specificRequest.

어떤 상황에서 사용할 수 있을까?

  • 하나의 서비스에서 여러 플랫폼 계정으로 로그인 기능을 지원하는 경우
    ex) 앱 내 계정 생성을 하고 로그인을 할 수 있도록 되어있는 서비스에서 추후에 소셜로그인 기능을 추가하고 싶다면?
    네이버, 카카오, 애플 모두 다른 형태일 수 있는데, 기존 로그인 서비스 프로토콜을 채택하는 Adapter를 사용하여 서비스를 확장할 수 있습니다.

  • 타사의 라이브러리나 모듈을 사용해야 할 때, 같은 프로젝트에서 다른 팀이 구현해놓은 클래스의 기능을 가져다 사용할 때
    ex) A팀이 신용카드를 등록하고 조회하는 클래스를 만들어 놓았습니다. B팀이 이 기능을 확장하고 싶다면 A팀의 클래스를 상속받는 Adapter를
    사용해 추가적인 기능을 구현할 수 있을 것 같습니다.

이미지 출처: https://www.slideshare.net/BillKim8/swift-adapter
참고 자료: https://icksw.tistory.com/240, https://qteveryday.tistory.com/303