어댑터 패턴은 아무 관련없는 인터페이스간의 호환성을 도와준다.
Context
사실 adapter
는 일상생활에서도 많이 볼 수 있는 형식과 같다. 흔히 말하는 돼지코와 같은 원리이다.
먼저 아래와 같은 상황이 주어진다고 해보자, 최초 어플리케이션 설계시 Client
는 Car
을 가지고 있으며 움직일 때마다
car::depart
메서드를 이용한다.
이를 코드로 나타내면 아래와 같다.
interface Car {
void move();
}
class Sonata implements Car {
@Override
public void move() {
System.out.println("Sonata is going!");
}
}
class Client {
private Car car;
public Client(Car car) {
this.car = car;
}
void move() {
this.car.move();
}
public static void main(String[] args) {
Client client = new Client(new Sonata());
client.move();
}
}
Problem
어느 날부터 Client
는 갑자기 기차를 이용하여 이동이 가능하다는 기능이 필요해졌다. 이미 스트레티지 패턴을 이용하여
나름 확장 가능하도록 설계했지만 구조적으로 풀 수 없는 문제이다.
사실 우리는 이미 근본적인 답을 알고 있다.
- 기차와 자동차 인터페이스의 상위 인터페이스를 만든다.
- 해당 상위 인터페이스를 클라이언트가 의존하도록 한다.
하지만 위와 같이 애플리케이션 구조의 전반을 흔드는 리팩토링을 한다면 모든 자동차와 기차의 구현 클래스와 의존 코드를 수정해야한다는 엄청난(?) 일을 수행해야한다.
위와같은 이유 때문에 개발자는 아래와 같은 막코딩(?)을 하게되고 어마어마한 쓰레기 남기게 된다. 심지어 기능 확장에
대해서 기존 Client
의 코드를 수정했으므로 OCP 위반이다.
moveViaTrain이라니 끔직하다.
interface Train {
void go();
}
class KTX implements Train {
@Override
public void go() {
System.out.println("KTX is going!");
}
}
class Client {
private Car car;
private Train train;
public Client(Car car, Train train) {
this.car = car;
this.train = train;
}
void moveViaCar() {
car.move();
}
void moveViaTrain() {
train.go();
}
public static void main(String[] args) {
Client client = new Client(new Sonata(), new KTX());
client.moveViaCar();
client.moveViaTrain();
}
}
Solution
흔이 외국에 여행할 때 사용하는 돼지코와 같은 원리다! 돼지코는 기존의 포트의 모양을 적절한 위치의 적절한 모양으로
위임한다. Adapter Pattern
도 이와 같은 내용이다. 호환시켜야하는 인터페이스를 적절하게 위임한다.
UML의 CarAdapter
가 이 내용을 담당한다. Train
타입 필드를 가져 Train::go
를 Car::depart
메서드로 위임시켜버리는
코드를 가지고있다. (메서드명이 좀 이상하다. 명확히 구분하기 위해 이렇게 작성했다.)
class CarAdapter implements Car {
private Train train;
public CarAdapter(Train train) {
this.train = train;
}
@Override
public void move() {
train.go();
}
}
위와 같은 형식으로 어댑터를 만들면 최초 Context의 Client
코드를 수정하지 않고 아래와 같이 쓸 수 있게된다.
class Client {
private Car car;
public Client(Car car) {
this.car = car;
}
void move() {
car.move();
}
public static void main(String[] args) {
Client client = new Client(new CarAdapter(new KTX())); // Adaptee 되었다.
client.move();
}
}
위의 경우 Train
, Car
는 공통된 이동수단이라는 부분을 추상화할 수 있다. 하지만 어댑터 패턴은 이런 경우 뿐만 아니라
정말 아무 관련없는 인터페이스 간의 호환성을 보장하기 위해서도 쓰일 수 있다는 것을 참고하자.
Summary
어댑터 패턴은 아무런 관계 없는 인터페이스간의 호환성을 보장하기 위해 사용된다.
- 어댑터 패턴은 OCP를 만족한다. 별다른 의존코드의 변경없이 잘 동작한다.
- 패치수준의 패턴이다. 어플리케이션의 리팩토링의 범위와 리스크가 매우 크다고 판단할 때 쓰며 의외로 잘 동작한다.
'소프트웨어 > 디자인패턴' 카테고리의 다른 글
[소프트웨어/디자인패턴] 컴포지트 패턴 (Composite Pattern) (0) | 2017.02.25 |
---|---|
[소프트웨어/디자인패턴] 퍼사드 패턴 (Facade Pattern) (0) | 2017.02.24 |
[소프트웨어/디자인패턴] 팩토리 메서드 패턴(Factory Method Pattern) (0) | 2017.01.27 |
[소프트웨어/디자인패턴] 템플릿 메서드 패턴(Template Method Pattern) (0) | 2017.01.26 |
[소프트웨어/디자인패턴] 데커레이터 패턴(Decorator Pattern) (0) | 2017.01.25 |