이런거 저런거 그런거

어댑터(Adaptor) 패턴 본문

카테고리 없음

어댑터(Adaptor) 패턴

빵진 2021. 5. 16. 16:34
클래스의 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다.
인터페이스가 호환되지 않아 쓸 수 없었던 클래스들을 같이 사용 할 수 있게 해 줍니다.
Head First 디자인패턴 p.308

 


 

  • Vendor Class : Existing System이 사용하는 인터페이스를 지원하지 않는 클래스
  • Adapter : Vendor Class가 Existing System이 사용하는 인터페이스를 지원하는 것처럼 보이게 해 주는 클래스

따라서 어댑터에서는

  • 기존 시스템(클라이언트)에서 사용하는 인터페이스를 구현한다.
  • 인터페이스 구현은 벤더클래스에서 제공하는 인터페이스를 이용하여 구현한다.

역할만 보자면 C에서 간혹 구현해서 사용하는 Wrapper 함수와 유사하다.

그래서 Wrapper패턴으로 불리기도 한다.

GoF디자인 패턴의 분류에 따르면 구조(Structural) 패턴에 속한다.

※ 구조(Structural) 패턴
클래스나 객체를 조합해 더 큰 구조를 만드는 패턴

 

디자인 패턴 Card Head First (p.281)

 

  • Adaptee : (Client가) 원하는 인터페이스를 지원하지 않는 클래스
  • Target : Client 동작에 필요한 인터페이스. 즉, Adaptee가 지원하길 바라는 인터페이스
  • Adapter : Adaptee가 Target인터페이스를 지원하는 것 처럼 보이게 해 주는 클래스

어떤 경우에 사용 ?

  • 기존 시스템(클라이언트)과 벤더클래스 수정 없이 둘 사이를 결합시켜 동작 시킬 수 있다.

사용하면 어떤 장점이 ?

  • 클라이언트와 벤더클래스 간 의존성을 줄일 수 있어 결합도를 낮추고 응집도가 높은 설계가 가능하다.
    - 결합도를 낮춤 : 한쪽의 변경이 다른 한쪽에게 영향을 주지 않음
    - 응집도가 높음 : 클라이언트/어댑터/어댑티 각각의 역할이 구분되어 각자의 기능에만 집중하여 구현 가능

어떻게 ?

  • 의존 역전 원칙(DIP)
  • 동작흐름
    • 클라이언트 >> 어댑터(인터페이스) >> 어댑터(구상클래스) >> 벤더클래스
  • 의존성흐름
    • 클라이언트 >> 어댑터(인터페이스) << 어댑터(구상클래스) >> 벤더클래스

따라서 클라이언트는 반드시 인터페이스에 맞춰서 프로그래밍되어야 한다.

※ "디자인원칙 : 특정 구현이 아닌, 인터페이스에 맞춰서 프로그래밍한다." (p.113)

 

책 예제(p.276)

  • 클라이언트(오리) > 물오리  (가능)
  • 클라이언트(오리) > 야생칠면조 (불가)

클라이언트에서 야생칠면조를 사용가능하게 하기 위해 칠면조어댑터를 추가하였다.

  • 클라이언트(오리) > 칠면조어댑터 > 야생칠면조
더보기

이런 예제라면 어떨까?

  • 클라이언트(USB) > USB키보드 (가능)
  • 클라이언트(USB) > PS/2키보드 (불가)
  • 클라이언트(USB) > PS/2 USB어댑터 > PS/2키보드

칠면조 어댑터 예(p.276)

따라서 클라이언트에서는 어댑티의 존재를 알지 못한다.

 

바보같은 질문은 없습니다(p.280)

Q) 어댑터에서 얼마나 적응을 시켜줘야하나요? 대형 타겟 인터페이스를 구현해야 되는 경우에는 할 일이 정말 많아질 수 있을 것 같은데요?

> 어댑터 구현 양은 인터페이스 크기에 비례함. 따라서 많은 양인 경우 어댑터 없는 설계를 고려해보아도 좋음
Q) 한 어댑터에서 한 클래스만 감싸야 하나요?
> 경우에 따라 두 개 이상의 어댑티를 감싸야 하는 상황이 생길 수도 있음(ex> 
Q) ... 시스템(Client) 내에 어댑티를 이용하는 방법이 2종류라면?
  • Client(옛날 구현부분) > 어댑터 > 어댑티
  • Client(최근 구현부분) > 어댑티
> 이 경우 다중 어댑터를 만들수도 있음.
> 즉, 어댑터에는 본래 어댑터 기능에 더해 최신 구현부분을 위한 인터페이스도 추가
> (이 경우 최신 구현부분의 기능은 바이패스역할정도일듯)

 

어댑터 구현 방식 (p.281)

객체 (Object)
어댑터
설명 어댑티를 적응시키는데 있어서 구성을 사용하는 방식
장점 - 어댑티의 모든 서브클래스에 대해 어댑터 적용이 가능하다.
- 실행 중에 동적으로 어댑티를 변경 할 수 있다. (스트래티지 패턴)
- 어댑티와 어댑터 객체화 시점이 달라도 된다.
단점 - 어댑티 객체를 별도로 생성하고 등록하는 과정이 필요하다. (관리 할 객체 및 코드 증가)
클래스(Class)
어댑터
설명 어댑티를 적응시키는데 있어서 상속을 사용하는 방식
장점 - 어댑터가 어댑티의 서브클래스라 어댑티의 행동을 오버라이드 할 수 있다.
- 어댑티 객체를 만들 필요 없다. (코드 단순화)
단점 - 상속을 사용하기에 어댑터 클래스는 특정 어댑티 클래스에만 적용 가능하며 컴파일시 고정된다.
- 어댑티는 구상클래스여야 하며 동적으로 변경 불가하다.

 

객체 어댑터와 클래스 어댑터

※ 클래스 어댑터의 다중 상속 여부에 따른 사용 제약

"Head First"책에는 다중 상속이 가능한 경우(e.g. C++)에만 클래스 어댑터 사용가능하다고 쓰여져있지만, 어댑티는 상속하여 구현하고 Target으로부터는 인터페이스로 구현하면 다중 상속이 불가능한 Java에서도 사용 가능함.

※ 스트래티지 패턴
행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴

커맨드 패턴과의 유사성

어댑터 패턴 (구조 패턴) 커맨드 패턴 (행위 패턴)
행위 (Behavioral) 패턴
객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴
작업을 여러 객체들에 대해 분배하면서 서로간의 의존성을 최소화

커맨드 패턴 (p.245)

어댑터 패턴 SW 변경 없이 클래스 추가만으로 인터페이스 호환을 맞추기 위한 패턴
커맨드 패턴 행위를 캡슐화 함으로서 주어진 여러 기능들을 실행 할 수 있는 재사용성이 높은 클래스를 설계하는 패턴

데코레이터 패턴과의 차이점

 

비슷하게 볼 수 있는 부분

  • 클래스를 감싸고 있는 부분을 구현함으로서 기존 코드를 수정하지 않고 사용 할 수 있도록 한다.

 

차이점

  • 데코레이터
    • 목적 : SW 변경 없이 기능(행동)을 유연하게 추가 시켜주기 위한 목적
    • 즉, 인터페이스를 변환시키지 않고 행동만 더 함 (Client obj A() -> DecoA.A() -> DecoB.A() -> Obj.A())
    • Client에서는 데코레이터의 존재를 모름
  • 어댑터
    • 목적 : SW 변경 없이 중간에서 인터페이스를 변경하여 호환시켜주기 위한 목적
    • 행동은 추가하지 않고 인터페이스만 변화시킴(Client obj A() -> Adap.A() -> Obj.B())
    • Client에서는 어댑티의 존재를 모름

커맨드 패턴에 나오는 홈 오토메이션 리모컨 디자인에서

터치리모컨을 추가 해 보는 예


사용예) Android ArrayAdapter

https://developer.android.com/reference/android/widget/ArrayAdapter

https://cheaps.fashionstore2021.com/content?c=listview%20adapter&id=40

 

 

 

 

References

Head First Design Patterns 스토리가 있는 패턴 학습법, 한빛미디어
https://gmlwjd9405.github.io
https://hamait.tistory.com/364?category=131245
https://www.scribd.com/document/7534738/Design-Patterns-Quick-Reference-Card
Comments