티스토리 뷰
사용자는 C 타입 형태만 지원하는 맥북을 구매했습니다. 사용자가 USB 포트를 사용하려면, USB와 C 타입을 호환할 수 있는 어댑터가 필요하게 됩니다. 사용자는 어댑터를 통해 어떠한 USB 포트를 가져와도 문제없이 C 타입의 맥북을 사용할 수 있습니다. 이처럼 어댑터 패턴은 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 동작하도록 해주는 패턴입니다.
- Client
- Adaptee를 사용하려는 사용자를 의미한다.
- Adaptee
- 라이브러리나 외부 시스템을 의미한다.
- Adapter
- Client와 Adaptee 중간에서 호환성이 없는 둘을 연결시켜주는 역할이다.
- Target Interface를 구현하며, Client는 TargetInterface를 통해 Adapter에 요청을 보낸다.
- Adapter는 Client의 요청을 Adaptee가 이해할 수 있는 방법으로 전달한다.
- Target Interface
- Adapter가 구현해야 할 인터페이스이며, Client는 Target Interface를 통해 Adaptee를 사용할 수 있다.
어댑터 패턴 예시
FrontController 클래스에서는 현재 ControllerV1을 사용하고 있습니다. 새롭게 추가되는 Controller가 있다면, FrontController에서는 추가되는 Controller 마다 호환을 위해 로직을 수정해야 하기 때문에, 유연하지 못한 구조를 가지고 있습니다.
ControllerV1
public interface ControllerV1 {
public void process();
}
public class MyControllerV1 implements ControllerV1 {
@Override
public void process() {
System.out.println("ControllerV1");
}
}
FrontController
public class FrontController {
private final Map<String, Object> controllerMappingMap = new HashMap<>();
public FrontController() {
controllerMappingMap.put("controllerV1", new MyControllerV1());
}
public void use(String controllerName) {
// ControllerV1을 사용한다.
Object handler = controllerMappingMap(controllerName);
handler.process();
}
}
시간이 지나 ControllerV2를 개발하게 되었고, 이를 적용하는 상황이 왔습니다. 현재 사용자들은 ContollerV1을 사용하고 있는 상황이며, 특정 사용자는 계속해서 사용할 수도 있다는 부분을 생각하면, 유지 보수하기가 굉장히 까다롭다고 느껴질 수 있습니다. 이러한 상황에서 어댑터 패턴을 적용하면, 유연하게 문제를 해결할 수 있습니다.
추가되는 ContollerV2
public interface ControllerV2 {
public String process();
}
public class MyControllerV2 implements ControllerV2 {
@Override
public String process() {
System.out.println("ControllerV2");
return "V2";
}
}
ControllerAdapter
public interface ControllerAdapter {
public boolean isSupport(Object handler);
public void handle(Object handler);
}
// 기존 ControllerV1의 어댑터를 생성한다.
public class ControllerV1Adapter implements ControllerAdapter {
@Override
public boolean isSupport(Object handler) {
return (handler instanceof ControllerV1);
}
@Override
public void handle(Object handler) {
// ControllerV1을 사용한다.
ControllerV1 controller = (ControllerV1) handler;
controller.process();
}
}
// 새롭게 추가되는 ControllerV2의 어댑터를 생성한다.
public class ControllerV2Adapter implements ControllerAdapter {
@Override
public boolean isSupport(Object handler) {
return (handler instanceof ControllerV2);
}
@Override
public void handle(Object handler) {
// ControllerV2를 사용한다.
ControllerV2 controller = (ControllerV2) handler;
controller.process();
}
}
FrontController 클래스에서는 이제 ControllerAdapter를 통해 원하는 Controller를 찾아서, 사용할 수 있는 구조를 갖게 됩니다.
FrontController
public class FrontController {
private final Map<String, Object> controllerMappingMap = new HashMap<>();
private final List<ControllerAdapter> controllerAdapters = new ArrayList<>();
public FrontController() {
controllerMappingMap.put("controllerV1", new MyControllerV1());
controllerMappingMap.put("controllerV2", new MyControllerV2());
controllerAdapters.add(new ControllerV1Adapter());
controllerAdapters.add(new ControllerV2Adapter());
}
public void use(String controllerName) {
// ControllerV1, ControllerV2 중에서 사용하려는 Controller를 찾는다.
Object handler = controllerMappingMap(controllerName);
// 사용하려는 Controller의 Adapter를 찾는다.
ControllerAdapter adapter = getControllerAdapter(handler);
// ControllerAdapter를 실행하여, Adaptee에서 원하는 작업을 처리한다.
adapter.handle(handler);
}
private ControllerAdapter getControllerAdapter(Object handler) {
for (ControllerAdapter controllerAdapter : controllerAdapters) {
if (controllerAdapter.supports(handler)) return controllerAdapter;
}
throw new IllegalArgumentException("ControllerAdapter 찾을 수 없습니다.");
}
}
위와 같이 어댑터 패턴을 통해 새로운 Adaptee가 추가되어도 Client와 Adpatee가 호환되는 유연한 구조를 만들 수 있습니다.
참고
'디자인 패턴' 카테고리의 다른 글
템플릿 메서드 패턴 (Template Method Pattern) (0) | 2022.04.28 |
---|---|
전략 패턴 (Strategy Parttern) (0) | 2021.09.16 |
댓글