[Design Pattern] Adapter 어댑터패턴

이은수, Lee EunSoo·2024년 9월 26일
0

DesignPattern

목록 보기
3/12
post-thumbnail
post-custom-banner

개요

Adapter patternWrapper Pattern이라고도 한다.

어댑터 패턴은 기존의 코드를 유지하면서 새로운 프레임워크나 코드를 적용하기 위해서 사용하는 디자인 패턴을 말한다.

마치 우리가 전자제품에서 사용하는 직류와 교류를 변환해주는 어댑터 처럼 중간에서 코드를 변환해주는 기능을 하는 형태를 말한다.

형태는 두가지 형태로 나뉜다.

  1. 상속을 이용한 방법
  2. 위임을 이용한 방법

설명

어떤경우에 사용하는가?

이 패턴은 어떤 클래스의 인터페이스를 클라이언트가 원하는 인터페이스로 변환하고자 할때 사용한다.
즉 이미 존재하는 클래스를 client가 원하는 형태의 인터페이스로 사용하고자 할때 사용한다는 말이다.

특징

  1. 너무 기능이 서로 동떨어진 클래스는 이 패턴을 적용할 수 없다.
  2. Adaptee클래스의 소스코드가 없더라도 사양만 안다면 패턴을 적용 할 수 있다.
  3. 다른 프로그램과의 연결뿐만 아니라 같은 프로그램이라도 버전이 다른경우 생기는 문제를 해결하기 위해서 사용되기도 한다.

java

상속을 이용한 방법


상속을 이용한 방법은 어댑터클래스가 어댑티 클래스를 상속받는 형태이다.

역할소개

  • Target
    • 지금 필요한 메소드를 정의, 여기에 정의된 메소드의 이름으로 Client에서 사용함
    • 아래 예제에서 Print인터페이스가 이 역할을 함
  • Client
    • Main문의 역할, Target을 이용하는 존재
  • Adapter
    • Target과 Adaptee를 연결해 주는 존재
    • 클래스를 변환해 주는 역할을 한다.
    • 아래 코드에서 PrintBanner가 이 역할을 한다.
  • Adaptee
    • 변환이 필요한 존재
    • 아래코드에서 Banner클래스가 이 역할을 한다.

예제 코드

아래 코드는 다음 다이어그램과 같은 형태의 코드이다.

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)클래스는 상속이 아닌 객체를 생성해서 사용한다는 점

형태만 다를 뿐 동작은 두방식 모두 동일하다.

in Swift

이 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 코드를 수정하지 않고 새로운 서비스를 할 수 있다는게 제일 큰 이유 일것이다.

또한 이렇게 코드를 클래스 하나를 띄워서 관리하면 재사용도 용이하고 프로그램 검사에 매우 용이하다는 장점이 있다고 한다.

상속? 위임?

두가지 방법이 있는데 어떤 방법을 사용하는게 맞을까?

일반적으로는 위임을 사용하는것을 많이들 권장한다고 한다. 상속의 경우 상위클래스의 동작을 제대로 모르면 상속을 효과적으로 이용하기 어렵기 때문이다.

참고자료

java언어로 배우는 디자인패턴 입문 3판 (유키 히로시 저)

profile
iOS 개발자 취준생, 천 리 길도 한 걸음부터
post-custom-banner

0개의 댓글