1. 어댑터 패턴이란

1.1 어댑터 패턴의 정의

특정 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환하는 패턴


어댑터 패턴은 클라이언트가 요구하는 인터페이스와 호환되지 않는 인터페이스를 가져서 함께 동작 할 수 없는 클래스를 함께 동작 할 수 있게 해준다.

그리고 어댑터패턴은 오브젝트 어댑터(Object Adapter)와 클래스 어댑터(Class Adapter)로 구현방법이 나뉘어지는데,  클래스 어댑터가 가지는 몇 가지 단점 때문에 이 글에서는 오브젝트 어댑터를 주로 다루려고 한다.



1.2 어댑터 패턴에 대한 대략적 이해


A라는 클래스를 B라는 클래스와 함께 동작하게 하고 싶지만,  B 클래스와 함께 동작하려면 C 인터페이스 형태를 따르도록 구현되어야 한다.

하지만 기존 코드를 수정하는 것(A 클래스가 C 인터페이스를 상속하도록 수정하거나 B 클래스가 A 클래스와 함께 동작할 수 있도록 수정하는 것)은 비효율적이고 오류를 일으키는 원인이 되는 일이므로 A 클래스를 C 인터페이스에 맞게 변환해주는 Adapter를 중간에 추가하여 문제를 해결한다.



그러면 A 클래스가 B 클래스와 함께 동작 할 수 있다.


1.3 오브젝트 어댑터(Object Adapter)

오브젝트 어댑터는 구성(Composition)을 사용하여 어댑터 패턴을 구현한다. UML과 같이 보도록 하자.



Client - Target 인터페이스를 요구하는 요소를 지닌 클래스

Target - 어떤 인터페이스

Adaptee - Client의 Target 인터페이스를 요구하는 요소에 집어넣고 싶은 클래스. Target 인터페이스에 호환되지 않음.

Adapter - Adaptee 클래스를 Target 인터페이스에 맞춰주는 클래스


여기서 Adapter는 Target 인터페이스의 request() 메소드를 구현 하기 위해 Adaptee 클래스의 method1() 메소드를 사용 할 것이다.

그러면 Client는 Adapter 클래스를 통해 Adaptee를 Target 인터페이스의 구상 클래스처럼 사용할 수 있다.


1.4 클래스 어댑터(Class Adapter)

클래스 어댑터는 상속(Inheritance)를 사용하여 어댑터 패턴을 구현한다. 이것도 UML과 같이 보도록 하자.



Client - Target 인터페이스를 요구하는 요소를 지닌 클래스

Target - 어떤 인터페이스

Adaptee - Client의 Target 인터페이스를 요구하는 요소에 집어넣고 싶은 클래스. Target 인터페이스에 호환되지 않음.

Adapter - Adaptee 클래스를 Target 인터페이스에 맞춰주는 클래스


클래스 어댑터는 다중 상속을 사용한다.

클래스 어댑터 패턴에서는 Adapter가 Target 클래스와 Adaptee 클래스를 둘 다 상속하여 Target 클래스가 필요한 곳에서도 사용 될 수 있고 Adaptee 클래스가 필요한 곳에서도 사용 될 수  있게 한다.


2. 예제

게임을 개발했다고 생각 해 보자.

개발하려는 게임은 정말 간단해서, 몬스터와 액티브 아이템, 그리고 UI로만 구성된다.


몬스터와 액티브아이템, UI는 각각 다른 인터페이스로 구현된다.

개발 초기에 클래스 다이어그램은 다음과 같을 것이다.




유저 인터페이스 클래스에서는 setActiveItem() 메소드로 사용할 액티브아이템을 결정하고, useItem() 메소드를 실행하면 ActiveItem 인터페이스의 useItem() 메소드를 이용하여 액티브아이템의 효과를 사용한다.


몬스터는 grawl() 메소드를 사용하면 울부짖는 소리를 낸다. 울부짖을때는 grawlSound 어트리뷰트에 저장된 소리로 울부짖는다.


그런데 게임 기획자가 새로운 아이디어를 떠올렸다. 유저가 몬스터를 포획하여 액티브아이템처럼 몬스터를 사용할 수 있게 하자는 것이다.

몬스터를 포획하면 몬스터가 액티브아이템으로 등록되고, 이 아이템을 사용하면 유저에게 몬스터가 울부짖는 소리를 들려준다.


이를 구현하고자 할 때 문제는 유저 몬스터 클래스는 유저 인터페이스의 setActiveItem() 메소드에 호환되지 않는다는 것이다.


위에서 언급했듯이, UserInterface에 setItem(Monster) 메소드를 추가하거나, Monster 인터페이스가 ActiveItem 인터페이스를 상속하게 하는 등 기존 코드를 수정하는 것은 좋은 선택이 아니다.


각각의 몬스터별로 새로운 아이템 클래스를 만드는 것도 좋은 방법이지만 그러면 몬스터의 숫자만큼 클래스의 수가 늘어나게 되기 때문에 더 좋은 방법이 있었으면 한다.


더 좋은 방법으로서 어댑터 패턴을 적용해보자.

몬스터를 액티브아이템처럼 사용될 수 있도록 적절한 어댑터를 만들어 클래스 구조를 다음과 같이 만든다.



MonsterToActiveItemAdapter가 바로 Monster 인터페이스를 ActiveItem 인터페이스처럼 사용 될 수 있게 해주는 클래스다.


MonsterToActiveItemAdapter 클래스의 useItem() 메소드는 Monster 인터페이스의 grawl() 메소드를 호출하도록 구현된다.

그래서 붙잡은 몬스터를 액티브아이템 처럼 사용 할 수 있다.


구현된 프로젝트는 다음의 깃허브 페이지에서 확인 할 수 있다.

(https://github.com/InvincibleTyphoon/AdapterPatternExample/tree/master)


3. 어댑터 패턴의 장점

- 인터페이스 호환성 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있음

- 상속이 아닌 구성(Composition)을 이용하기 때문에 Adaptee의 모든 서브클래스에 대해 어댑터를 사용가능(Object Adapter에만 해당)


인터페이스가 호환되지 않는 문제가 발생해도 어댑터 패턴을 적용하면 기존의 코드를 수정하지 않고도 문제를 해결 할 수 있다.


Class Adapter에는 적용되지 않는 장점이지만, 상속이 아닌 구성을 사용하기 때문에 Adaptee의 모든 서브클래스에 대해 어댑터를 적용 할 수 있고, 실행중에 동적으로 Adaptee를 바꿀수도 있다.



4. 오브젝트 어댑터 VS 클래스 어댑터

위의 예제에서는 오브젝트 어댑터를 적용했다.

오브젝트 어댑터와 클래스 어댑터를 비교했을 때, 서로다른 장단점이 있는데 우선 그 차이점을 비교해보자.

 

 Ojbect Adapter

Class Adapter 

장점 

상속이 아닌 구성(Composition)을 사용하기 때문에 더 유연하다 

Adapter가 Adaptee의 서브클래스이기 때문에 Adaptee의 행동을 오버라이드 할 수 있다.


Adaptee 객체를 만들지 않아도 된다. 

단점

 Adaptee 객체를 만들어야 사용가능하다.

상속을 이용하기 때문에 한 Adapter 클래스가 특정 Adatee 클래스에만 적용가능하다.


다중상속이 지원되는 언어에서만 사용가능하다. 



Object Adapter는 상속이 아닌 구성을 사용하기 때문에 더 유연하다. 하지만 Adaptee 객체를 만들어야하기 때문에 Class Adapter에 비해 관리해야할 객체가 하나 늘어난다는 단점이 있다.


Class Adapter는 Adapter가 Adaptee의 서브클래스이기 때문에 Adaptee의 행동을 오버라이드하여 사용 할 수 있고. Adaptee 객체를 따로 만들지 않고 Adapter 객체를 만들어서 사용가능하다는 장점이 있다. 관리해야 할 객체가 하나 적다는 것이다.

하지만 Class Adapter는 상속을 사용하기 때문에 한 Adapter 클래스가 특정 Adaptee 클래스 하나에 대해서만 적용된다. 그 Adaptee클래스의 서브클래스에 대해서 적용 불가능하고, 실행 중간에 동적으로 변경도 불가능한 것이다.

그리고 이것은 치명적인 단점이라고 할 수 있는데, 자바처럼 다중상속이 지원되지 않는 언어에서는 사용이 불가능하다.


개인적인 견해로 말하자면 유연성 측면에서 Object Adapter의 장점이 Class Adapter의 장점보다 낫고, Object Adapter의 단점은 그다지 치명적이거나 하지 않다고 본다.

상황에 따라서 어떤 방법을 쓸 지 선택애햐겠지만, 이 두가지 방법 중 한가지를 선택해야하는 상황이라면 나는 Object Adapter를 사용할 것 같다.


Head First Design pattern에서 클래스 어댑터는 객체어댑터에 비해 Adaptee 전체를 다시 구현하지 않아도 된다는 장점을 가진다고 하는데, 이에 대해 객체 어댑터도 Adaptee에게 필요한 일을 시키는 코드만 구현하면 된다고 답하는 내용이 있는 걸로 봐서, 클래스 어댑터 패턴이 Adaptee에게 일을 시키는 코드를 구현하지 않다도 되는게 장점이라는 의미로 그렇게 언급하는 것 같다.





5. 레퍼런스

- Head First Design Pattern(O'REILLY media)

- 위키피디아 Adapter Pattern(https://en.wikipedia.org/wiki/Adapter_pattern)

블로그 이미지

서기리보이

,