Tiny Star

🪄Interview/✏️Study

[CS STUDY INTERVIEW] 13주차 - 어댑터 패턴

청크 2024. 6. 5. 20:25

CS 스터디 13주차 

 

어댑터 패턴

 

어느새 첫 목표였던 JAVA 관련 스터디가 모두 끝나고 

개발을 하면서 필수적으로 공부해야하는 패턴관련하여 새로운 스터디가 시작되었다.


어댑터 패턴(Adapter Pattern)

어댑터 패턴(Adapter Pattern)은 소프트웨어 설계 패턴 중 하나로, 기존 클래스의 인터페이스를 다른 인터페이스로 변환하여 

서로 호환되지 않는 인터페이스를 가진 클래스들이 함께 동작할 수 있게 만드는 패턴이다.

어댑터 패턴은 주로 기존 코드의 재사용을 목적으로 하며, 특히 시스템을 새롭게 설계하거나 기존 시스템에 새로운 기능을 추가할 때 유용하다.

 

어댑터 패턴은 두 개의 주요 요소로 구성되어 있는데, 타겟과 어댑터 그리고 어댑티가 있다.

 

1. 타겟 인터페이스(Target Interface)

클라이언트가 기대하는 인터페이스로, 클라이언트가 사용하고자 하는 메서드들을 정의한다.

 

클라이언트 코드가 어댑터를 통해 접근할 때 사용되는 인터페이스로,

클라이언트는 이 인터페이스만 알고있고 실제로 어떤 구현체가 사용되는지 알지 못하고

어댑터는 이 인터페이스를 구현하여 클라이언트에게 호환성을 제공한다.

 

JAVA

public interface Target {
    void request();
}

 

PYTHON

class Target:
    def request(self):
        pass

 

2. 어댑티 (Adaptee)

기존 클래스 또는 시스템으로, 타겟 인터페이스와 호환되지 않는 인터페이스를 가진다.

 

어댑티 클래스는 원래의 기능을 제공한다.

이 클래스는 변경되지 않고 그대로 유지되며, 어댑터를 통해 간접적으로 호출되어야하며

어댑티의 메서드는 타겟 인터페이스의 메서드와 다르기 때문에 직접적으로 사용할 수 없다.

 

JAVA

public class Adaptee {
    public void specificRequest() {
        System.out.println("Called specificRequest()");
    }
}

 

PYTHON

class Adaptee:
    def specific_request(self):
        print("Called specific_request()")

 

3. 어댑터 (Adapter)

타겟 인터페이스를 구현하고 어댑티 클래스를 감싸는 클래스이다.

어댑터는 타겟 인터페이스의 메서드 호출을 어댑티 클래스의 메서드 호출로 변환한다.

 

어댑터는 타겟 인터페이스를 구현하여 클라이언트에게 필요한 인터페이스를 제공하며

동시에 어댑티 객체를 내부에 포함하여 어댑티의 메서드를 호출한다.

이를 통해 클라이언트는 타겟 인터페이스를 사용하여 어댑티의 기능을 사용할 수 있다.

 

JAVA

public class Adapter implements Target {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specificRequest();
    }
}

 

PYTHON

class Adapter(Target):
    def __init__(self, adaptee):
        self.adaptee = adaptee

    def request(self):
        self.adaptee.specific_request()

어댑터 패턴의 종류

어댑터 패턴의 종류는 클래스 어댑터와 객체 어댑터. 두 가지 유형으로 나뉜다.

 

1. 클래스 어댑터(Class Adapter)

클래스 어댑터는 다중 상속을 통해 구현되는 어댑터로, 기존 클래스와 원하는 인터페이스를 모두 상속받아

개발자가 원하는 인터페이스에 맞게 동작하도록 메서드를 재정의한다.

 

 어댑터 클래스가 어댑티 클래스+타겟 인터페이스를 모두 상속받아야 하므로 다중 상속을 지원하는 언어에서만 사용할 수 있다.

 

2. 객체 어댑터(Object Adapter)

객체 어댑터는 구성을 통해 어댑터를 구현하는 방식으로 어댑터 클래스가 어댑티 클래스를 멤버 변수로 포함하여

그 인스턴스를 사용하는 방식으로 대부분의 객체 지향 언어에서 사용이 가능한 방식이다.


어댑터 패턴의 장단점

장점

1. 기존 클래스를 수정하지 않고도 다른 인터페이스와 호환이 되도록 호환성을 제공한다.

2. 기존 코드의 재사용을 촉진하여 재사용성이 향상된다.

3. 새로운 인터페이스를 쉽게 추가하거나 기존 인터페이스를 변경할 수 있어 코드의 유연성이 증가한다.

4. 클래스의 주요 책임을 변경하지 않고 어댑터를 통해 다른 책임을 추가할 수 있어 단일 책임 원칙을 지킨다.

 

단점

1. 어댑터 클래스가 많아지면 코드 복잡도가 증가한다.

2. 어댑터를 통해 호출을 감싸야 하기 때문에 약간의 성능 오버헤드가 우려되기도 한다.


자바와 파이썬에서의 어댑터 패턴 사용

가장 많이 사용되는 두 언어에서 어댑터 패턴이 어떻게 사용되는지 예제를 작성해봤다.

 

전체적인 동작 흐름을 정리해보고 구현해보면 이렇다.

 

1. 클라이언트(Client): 클라이언트는 타겟 인터페이스(Target)를 통해 시스템과 상호작용한다.

클라이언트는 어댑티(Adaptee)의 존재를 모르고, 어댑터를 통해 간접적으로 어댑티의 기능을 사용한다.


2. 타겟 인터페이스(Target Interface): 클라이언트가 기대하는 인터페이스로, 클라이언트가 호출하는 메서드들을 정의한다.


3. 어댑티(Adaptee): 기존 클래스 또는 시스템으로, 타겟 인터페이스와 호환되지 않는 인터페이스를 가지고 있어 실제 기능을 제공한다.


4. 어댑터(Adapter): 타겟 인터페이스를 구현하고 어댑티를 감싸는 클래스이며

어댑터는 클라이언트의 요청을 받아 어댑티의 메서드를 호출하는 역할을 한다.

 

자바(JAVA)

1. Target Interface

Target 인터페이스는 클라이언트가 사용하려는 인터페이스로, 여기서는 request라는 메서드를 정의하고 있다.

public interface Target {
    void request();
}

 

2. Adaptee Class

Adaptee 클래스는 기존의 클래스로 이 클래스는 specificRequest라는 메서드를 가지고 있으며,

이 메서드는 현재 Target 인터페이스와 호환되지 않는 상태이다.

public class Adaptee {
    public void specificRequest() {
        System.out.println("Called specificRequest()");
    }
}

 

3. Adapter Class

Adapter 클래스는 Target 인터페이스를 구현한다.

이 클래스는 Adaptee 클래스를 구성 요소로 포함하고 있으며, request 메서드 호출을 specificRequest 메서드 호출로 변환하고

어댑터 생성자는 Adaptee 인스턴스를 인자로 받아 이를 내부 변수로 저장하는 로직이다.

public class Adapter implements Target {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specificRequest();
    }
}

 

4. Cilent

Client 클래스는 어댑터 패턴을 실제 사용하는 클라이언트 코드이다.

Adaptee 인스턴스를 생성하고 이를 Adapter로 감싸 Target 인터페이스를 통해 사용하는 로직이며,

request 메서드를 호출하면 어댑터가 specificRequest 메서드를 호출하도록 변환한다.

public class Client {
    public static void main(String[] args) {
        Adaptee adaptee = new Adaptee();
        Target target = new Adapter(adaptee);
        target.request();  // Output: Called specificRequest()
    }
}

 

파이썬(PYTHON)

만약 동일한 패턴을 파이썬에서 사용하면 어떻게 사용될까.

스터디 주언어는 자바기준이지만 요새 가장 많이 쓰는 언어라, 파이썬도 추가해봤다.

 

1. Target Interface

Target 클래스를 만들어 클라이언트가 사용하는 인터페이스를 정의한다.

request 메서드를 정의하고 있지만 구현을 하지는 않았다.

class Target:
    def request(self):
        pass

 

2. Adaptee Class

Adaptee 클래스는 기존의 클래스임은 자바와 동일하다.

이 클래스는 specific_request라는 메서드를 가지고 있으며, 이 메서드는 현재 Target 인터페이스와 호환되지 않는 상태이다.

class Adaptee:
    def specific_request(self):
        print("Called specific_request()")

 

3. Adapter Class

Adapter 클래스는 Target 클래스를 상속받아 request 메서드를 구현한다.

이 클래스는 Adaptee 인스턴스를 구성 요소로 포함하고 있으며, request 메서드 호출을 specific_request 메서드 호출로 변환하고

생성자는 Adaptee 인스턴스를 인자로 받아 이를 내부 변수로 저장한다.

class Adapter(Target):
    def __init__(self, adaptee):
        self.adaptee = adaptee

    def request(self):
        self.adaptee.specific_request()

 

4. Cilent

Adaptee 인스턴스를 생성하고 이를 Adapter로 감싸 Target 인터페이스를 통해 사용하는데,

request 메서드를 호출하면 어댑터가 specific_request 메서드를 호출하도록 변환한다.

if __name__ == "__main__":
    adaptee = Adaptee()
    target = Adapter(adaptee)
    target.request()  # Output: Called specific_request()

 

 

어댑터 패턴은 소프트웨어 설계에서 매우 유용한 패턴으로, 특히 기존 시스템에 새로운 기능을 추가하거나 시스템 간의 호환성을 제공할 때 유용하다.

 

어댑터 패턴의 주요 목적은 인터페이스 변환을 통해 클래스 간의 호환성을 높이고 코드의 재사용성과 유연성을 증가시키데 있으며,

자바와 파이썬 모두에서 어댑터 패턴은 기존 클래스(Adaptee)의 인터페이스를 새로운 인터페이스(Target)로 변환하는 역할을 한다.

이를 통해 클라이언트 코드가 호환되지 않는 인터페이스를 가진 기존 클래스를 사용이 가능하다.

 

한 줄로 정리해보자면?

어댑터 클래스는 타겟 인터페이스를 구현하고 어댑티 클래스를 구성 요소로 포함하여,

타겟 인터페이스의 메서드 호출을 어댑티 클래스의 메서드 호출로 변환한다.