Adapter pattern 은 Wrapper Pattern이라고도 한다.
어댑터 패턴은 기존의 코드를 유지하면서 새로운 프레임워크나 코드를 적용하기 위해서 사용하는 디자인 패턴을 말한다.
마치 우리가 전자제품에서 사용하는 직류와 교류를 변환해주는 어댑터 처럼 중간에서 코드를 변환해주는 기능을 하는 형태를 말한다.
형태는 두가지 형태로 나뉜다.
- 상속을 이용한 방법
- 위임을 이용한 방법
이 패턴은 어떤 클래스의 인터페이스를 클라이언트가 원하는 인터페이스로 변환하고자 할때 사용한다.
즉 이미 존재하는 클래스를 client가 원하는 형태의 인터페이스로 사용하고자 할때 사용한다는 말이다.
상속을 이용한 방법은 어댑터클래스가 어댑티 클래스를 상속받는 형태이다.
아래 코드는 다음 다이어그램과 같은 형태의 코드이다.
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
public class PrintBanner extends Banner implements Print {
@Override
public void printWeak() { showWithParen(); }
@Override
public void printStrong() { showWithAster(); }
public PrintBanner(String string) { super(string); }
}
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
각각의 역할은 다음과 같다.
클라이언트는 Main클래스
Target은 Print인터페이스
adapter클래스는 PrintBanner클래스
adaptee클래스는 Banner클래스
어댑터 패턴에서 결국 하고자 하는것은 Main메소드 즉 Client의 코드를 최소한으로 변경하고 새로운 코드를 이용하고자 하기 위한것이다
PrintBanner클래스는 Banner클래스와 Main클래스의 변환장치(어댑터)의 역할을 하고 있는데
Banner클래스를 상속받고 Main클래스의 호출형태를 정의한 Print인터페이스를 구현하여 두 클래스를 성공적으로 연결하고 있다.
printWeak() <---> showWithParen()
printStrong() <---> showWithAster()
다음과 같이 메소드를 변환시키는 역할을 수행하고 있다.
위임 어댑터 패턴의 경우 형태는 위 다이어그램과 같다.
위임의 경우 Target을 인터페이스가 아닌 추상클래스로 사용한다.
그리고 Adaptee클래스를 상속받는것이 아닌 객체를 이용해서 사용한다.
//Adaptee
public class Banner {
private String string;
public Banner(String string) { this.string = string; }
public void showWithParen() { System.out.println("(" + string + ")"); }
public void showWithAster() { System.out.println("*" + string + "*"); }
}
//Adapter
public class PrintBanner extends Print {
private Banner banner;
public PrintBanner(String string) { this.banner = new Banner(string); }
@Override
public void printWeak() { banner.showWithParen(); }
@Override
public void printStrong() { banner.showWithAster(); }
}
//Target
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
//Client
public class Main {
public static void main(String[] args) {
//Print타입의 PrintBanner객체 생성
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
이 코드또한 상속을 이용한 방법과 동일하게 PrintBanner클래스가
printWeak() <---> showWithParen()
printStrong() <---> showWithAster()
이 메소드들을 연결하고 있는 구조이다.
다른점이라면 Target(Print)을 PrintBanner에서 상속받고, Banner(Adaptee)클래스는 상속이 아닌 객체를 생성해서 사용한다는 점
형태만 다를 뿐 동작은 두방식 모두 동일하다.
이 Print, PrintBanner, Banner, Main를 이용한 어댑터패턴을 Swift로 표현해 보자
class Banner{
var string: String
init(string: String) {
self.string = string
}
func showWithParen(){
print("( \(self.string) )")
}
func showWithAster(){
print("* \(self.string) *")
}
}
class PrintBanner : Banner, Print{
override init(string: String) {
super.init(string: string)
}
func printWeak() {
showWithParen()
}
func printStrong() {
showWithAster()
}
}
protocol Print{
func printWeak()
func printStrong()
}
var p: Print = PrintBanner(string: "Hello")
p.printWeak() // ( Hello )
p.printStrong() // * Hello *
//
// Adapeter02.swift
// DataStructure
//
// Created by lee eunsoo on 10/12/24.
//
// 위임 방식의 어댑터 패턴
import Foundation
class Banner1{
var string: String
init(string: String) {
self.string = string
}
func showWithParen(){
print("( \(self.string) )")
}
func showWithAster(){
print("* \(self.string) *")
}
}
class PrintBanner1 : Print1{
var banner: Banner1
init(string: String) {
banner = Banner1(string: string)
}
func printWeak() {
banner.showWithParen()
}
func printStrong() {
banner.showWithAster()
}
}
protocol Print1{
func printWeak()
func printStrong()
}
//
//var p: Print = PrintBanner(string: "Hello")
//p.printWeak() // ( Hello )
//p.printStrong() // * Hello *
왜 어댑터 패턴을 사용할까?
유지보수때문이다. 코드를 모두 백지부터 사용하면 좋겠지만 많은 경우 이미 구현된 코드(프레임워크)를 가져와서 사용하기 때문인데
Client코드와 adaptee코드가 동시에 개발되면 상관없을지도 모르나 따로 개발되고 각각 버전업을 하다보면 호환성에 문제가 생길 수 밖에 없다.
결론은 기존의 Clinet 코드를 수정하지 않고 새로운 서비스를 할 수 있다는게 제일 큰 이유 일것이다.
또한 이렇게 코드를 클래스 하나를 띄워서 관리하면 재사용도 용이하고 프로그램 검사에 매우 용이하다는 장점이 있다고 한다.
두가지 방법이 있는데 어떤 방법을 사용하는게 맞을까?
일반적으로는 위임을 사용하는것을 많이들 권장한다고 한다. 상속의 경우 상위클래스의 동작을 제대로 모르면 상속을 효과적으로 이용하기 어렵기 때문이다.