어댑터 패턴(Adapter/Wrapper Pattern)

ellyheetov·2022년 3월 14일
0
post-thumbnail

어댑터 패턴이란?

한 크래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다.
어댑터를 이용하여 인터페이스 호환성문제를 해결할 수 있다.

어탭터 패턴이 왜 필요할까?

  • 맥북에 포트는 다 C-Type이네??? Adapter가 필요함!

어댑터를 이용하면 호환되지 않는 충전기도 사용할 수 있다.

이처럼 어댑터 디자인 패턴을 사용하는 경우 서로 호환되지 않는 인터페이스를 사용하는 클라이언트를 그대로 활용할 수 있다.

인터페이스를 변환해주는 어댑터만 만들면 되니까!

이를 통해 클라이언트와 구현된 인터페이스를 분리할 수 있고, 나중에 인터페이스가 바뀌더라도 그 변경 내역은 어댑터에 캡슐화되기 때문에 클라이언트는 바뀔 필요가 없어진다.

언제 사용할 수 있을까?

  • 기존 클래스를 사용하고 싶은데 인터페이스가 맞지 않을 때
  • 이미 만든 것을 재사용하고자 하나 이 재사용 가능한 라이브러리를 수정할 수 없을 때

어댑터 패턴의 역할

  • 어떤 인터페이스를 클라이언트에서 요구하는 형태의 인터페이스에 적응 시켜주는 역할, 즉 중개인 역할
  • 한 인터페이스를 다른 인터페이스로 변환하기 위한 용도

주요 객체

  • Target: 인터페이스를 정의하는 클래스
  • Client: Target 인터페이스를 만족하는 객체와 동작할 대상
  • Adaptee: 적응 대상자. 인터페이스의 적응이 필요한 기존 클래스, 쉽게말해 호환되지 않는 클래스
  • Adapter: Target 인터페이스에 Adaptee 인터페이스를 적용시키는 클래스

어탭터 패턴의 예시

  1. Target: interface 정의

    protocol LightningPhone {
        func recharge()
        func useLightning()
    }
    protocol MicroUSBPhone {
        func recharge()
        func useMicroUSB()
    }
  2. Adaptee 정의

    class Iphone: LightningPhone {
        var connector: Bool = false
        public func recharge() {
            if (connector) {
                print("Recharge Started")
                print("Recharge finished")
            } else {
                print("Connect Lightning first")
            }
        }
        public func useLightning() {
            connector = true
            print("Lightning connected")
        }
    }
    class Android: MicroUSBPhone {
        var connector: Bool = false
    
        func recharge() {
            if (connector) {
                print("Recharge Started")
                print("Recharge finished")
            } else {
                print("Connect MocroUSB first")
            }
        }
    
        func useMicroUSB() {connector = true
            print("MicroUSB connected")
        }
    }
  3. Adapter 정의

    class LightningToMicroUSBAdapter: MicroUSBPhone {
        private final var lightningPhone: LightningPhone
    
        init(_ lightningPhone: LightningPhone) {
            self.lightningPhone = lightningPhone
        }
    
        public func lightningToMicroUSBAdapter(lightningPhone: LightningPhone) {
            self.lightningPhone = lightningPhone
        }
    
        func recharge() {
            lightningPhone.recharge()
        }
    
        func useMicroUSB() {
            print("MicroUSB connected")
            lightningPhone.useLightning()
        }
    }
  4. Clinet 정의

      func rechargeMicroUSBPhone(phone: MicroUSBPhone) {
        phone.useMicroUSB()
        phone.recharge()
    }
    func rechargeLightingPhone(phone: LightningPhone) {
        phone.useLightning()
        phone.recharge()
    }
    
    let android = Android()
    let iphone = Iphone()
    
    print("Recharging android with MicroUSB")
    rechargeMicroUSBPhone(phone: android)
    print("Recharging iphone with Lighting")
    rechargeLightingPhone(phone: iphone)
    
    print("Recharging iphone with MicroUSB")
    rechargeMicroUSBPhone(phone: LightningToMicroUSBAdapter(iphone)
  • 결과
    Recharging android with MicroUSB
    MicroUSB connected
    Recharge Started
    Recharge finished
    Recharging iphone with Lighting
    Lightning connected
    Recharge Started
    Recharge finished
    Recharging iphone with MicroUSB
    MicroUSB connected
    Lightning connected
    Recharge Started
    Recharge finished

어댑터 패턴의 장단점

장점

  • 기존 코드를 변경하지 않아도 된다.
  • 기존 코드를 변경하지 않기 때문에 클래스 재활용성을 증가시킬 수 있다.

단점

  • 구성요소를 위해 클래스를 증가시켜야 하기 때문에 복잡도가 증한다.
  • 클래스 Adapter 의 경우 상속을 사용하기 때문에 유연하지 못하다.
  • 객체 Adapter 의 경우는 대부분의 코드를 다시 작성해야 하기 때문에 효율적이지 못하다.

어댑터 패턴 사용시 고려할 점

  • 어댑터 클래스를 사용하기 위해 들어가는 비용이 얼마나 되나?
    - 작업량을 결정짓는 요인은 Target 인터페이스와 Adaptee 간에 얼마만큼의 유사성을 갖는가?
  • 대체 가능(Pluggable) 적응자, 누가 이 클래스를 사용할지에 대한 생각은 최소화
    - 모든 사용자에게 동일한 인터페이스르 제공하겠다는 생각은 넣어둬라. 어댑터 패턴을 통해 부담을 덜 수 있다.
profile
 iOS Developer 좋아하는 것만 해도 부족한 시간

0개의 댓글