도서 요약 / / 2023. 2. 13. 20:36

디자인 패턴 - 어댑터(adapter) 패턴

udemy 강좌(Java Design Patterns & SOLID Design Principles)를 정리한 내용이다.

https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/learn/lecture/9604610?start=0#overview


어댑터는 무엇인가?

어댑터 디자인 패턴을 알아보도록 하자. 이것은 구조적 디자인 패턴이며 왜 어댑터 디자인 패턴이 필요한지 알아볼 것이다.

  • 애플리케이션에서 객체가 하나 있고 클라이언트가 요구하는 기능을 제공하고 있다. 하지만 클라이언트 코드에서는 다른 인터페이스를 필요로 하고 있기 때문에 이 객체를 그대로 사용할 수가 없다. 이것이 어댑터 디자인 패턴이 어댑터를 사용해서 풀려고 하는 방식이다.
  • 어댑터 디자인 패턴을 사용하여 클라이언트에서 요구하는 인터페이스를 기존 객체에 적용함으로써 기존 객체가 클라이언트와 동작하게 만든다.
  • 이 패턴은 기존 객체를 감싸기 때문에 래퍼(wrapper)라고 부른다.

UML

어댑티(Adaptee)는 클라이언트가 요구하는 기능을 가지고 있다. myOperation 메소드가 있다. 하지만 클라이언트는 대상 인터페이스를 구현하는 객체를 사용해야 한다. 이 인터페이스는 operation이라는 메소드를 가지고 있다.

그래서 클라이언트에서 요구하는 기능을 가지고 있지만 이 인터페이스는 클라이언트에서 필요한 기능을 구현하고 있지 않다. 이러한 문제를 해결하기 위해 어댑터를 사용한다.

어댑터는 기능을 가지고 있는 기존 클래스를 확장하는 클래스이고 클라이언트에서 요구하는 대상 인터페이스를 구현한다.

어댑터 구현

  • 어댑터에 대한 클래스를 생성하면서 시작
    • 어댑터는 클라이언트에 필요한 인터페이스를 구현해야 한다.
    • 우선 기존 클래스를 확장함으로써 어댑터(Adapter)를 만들 수 있다.
    • 클래스 어댑터 구현에서 어댑티(Adaptee)를 상속받는 또 다른 메소드를 해당 메소드에 추가한다.
    • 오브젝트 어댑터에서는, 대상 인터페이스를 구현하고 어댑터(Adapter)에서 생성자로 어댑티(Adaptee)를 받는다. 예를 들면 합성을 사용한다.
  • 오브젝트 어댑터는 생성자 인수로 어댑티를 받거나, 덜 선호되는 방법이긴 하지만 특정 어댑티(Adaptee)와 강하게 결합된 생성자에서 초기화할 수 있다.

예제: UML (Class Adapter)

여기 BusinessCardDesigner 클래스가 있다. designCard라는 메소드가 있고 Customer 인터페이스를 구현할 객체가 필요하다. 또한 Customer 인터페이스가 있고 클라이언트에서 사용되는 여러 개의 메소드가 있다.

하지만 우리 코드에는 이미 Employee라는 클래스가 있다. 이 클래스에서 BusinessCardDesigner에서 필요한 모든 정보를 가지고 있다. 여기서 인터페이스가 서로 맞지 않다는 것을 알 수 있다. 그래서 EmployeeClassAdapter라는 클래스 어댑터를 만들어 볼 것이고 Employee 클래스를 확장하고 Customer 인터페이스를 구현하여 이 메소드의 구현체를 만들어 볼 것이다.

예제: UML (Object Adapter)

Object 어댑터에서도 동일한 문제가 있다. BusinessCardDesigner가 있고 Customer 인터페이스가 필요하고 기존 Employee 객체가 있다. 여기서 Customer 인터페이스를 구현하는 EmployeeObjectAdapter를 만들것이다. 그리고 생성자에서 Employee 객체를 받고 이 메소드의 구현체에서 Employee 객체를 위임하도록 해 볼 것이다.

Java 구현 - Class Adapter

@Getter
@Setter
public class Employee {

    private String fullName;

    private String jobTitle;

    private String officeLocation;
}

여기에 Employee 클래스가 있고 어댑티 클래스이다. 이것은 이 클래스 객체가 동작하거나 클라이언트 코드에서 필요한 기능을 제공한다는 의미이다. 이 클라이언트는 무엇인가?

public class BusinessCardDesigner {

    public String designCard(Customer customer) {
        String card = "";
        card += customer.getName();
        card += "\n" + customer.getDesignation();
        card += "\n" + customer.getAddress();

        return card;
    }
}

BusinessCardDesigner가 있고 이 클래스는 Customer에 대한 BusinessCard를 출력할 것이다. designCard라는 메소드가 있고 Customer 인터페이스를 구현하는 객체가 있고 BusinessCard를 출력하기 위해 객체에 메소드를 호출할 것이다.

public interface Customer {

    String getName();

    String getDesignation();

    String getAddress();
}

Customer 인터페이스를 무엇인가? 이것이 Customer 인터페이스이며 대상 인터페이스이다. 이것은 BusinessCardDesigner에서 필요한 몇 개의 메소드가 정의되어 있다. Employee 클래스를 볼 때 이름, Designatino, address와 같이 Customer가 필요한 정보를 알 수 있다. 하지만 그 기능은 다른 메소드가 제공된다.

public class EmployeeClassAdapter extends Employee implements Customer {
    @Override
    public String getName() {
        return this.getFullName();
    }

    @Override
    public String getDesignation() {
        return this.getJobTitle();
    }

    @Override
    public String getAddress() {
        return this.getOfficeLocation();
    }
}

Customer에서 제공된 메소드의 구현체를 제공해야만 한다. 이 메소드에 대한 스텁(Stub)을 만들어볼 것이 Customer 인터페이스에 정의된 이름을 가져와볼 것이다.

public class Main {

    public static void main(String[] args) {
        // Class Adapter 사용
        EmployeeClassAdapter adapter = new EmployeeClassAdapter();
        populateEmployeeData(adapter);
        BusinessCardDesigner designer = new BusinessCardDesigner();
        String card = designer.designCard(adapter);
        System.out.println(card);
    }

    private static void populateEmployeeData(Employee employee) {
        employee.setFullName("Elliot Alderson");
        employee.setJobTitle("Security Engineer");
        employee.setOfficeLocation("Allsafe Cybersecurity, New York City, New York");
    }
}

이제 main 메소드에서 이 클래스를 사용해볼 것이다.

Java 구현 - Object Adapter

@AllArgsConstructor
public class EmployeeObjectAdapter implements Customer {

    private Employee adaptee;

    @Override
    public String getName() {
        return adaptee.getFullName();
    }

    @Override
    public String getDesignation() {
        return adaptee.getJobTitle();
    }

    @Override
    public String getAddress() {
        return adaptee.getOfficeLocation();
    }
}

오브젝트 어댑터를 구현하기 위해서 클라이언트 코드가 필요로 하는 단지 대상 인터페이스를 구현하기만 하면 된다. Customer를 구현하기만 하고 Employee를 확장하지는 않는다. 그래서 Customer 인터페이스에서 정의된 메소드에 대한 스텁(Stub) 메소드를 만든다. 오브젝트 어댑터는 어댑티 객체가 필요하다.

public class Main {

    public static void main(String[] args) {
        // Object Adapter 사용
        Employee employee = new Employee();
        populateEmployeeData(employee);
        EmployeeObjectAdapter objectAdapter = new EmployeeObjectAdapter(employee);
        String card = designer.designCard(objectAdapter);
        System.out.println(card);
    }

    private static void populateEmployeeData(Employee employee) {
        employee.setFullName("Elliot Alderson");
        employee.setJobTitle("Security Engineer");
        employee.setOfficeLocation("Allsafe Cybersecurity, New York City, New York");
    }
}

main 메소드에서 오브젝트 어댑터를 사용해볼 것이다.

구현 고려사항

  • 어댑터가 잘 동작할지 여부는 대상 인터페이스와 적용될 객체 사이의 차이점에 따라 다르다. 만일 메소드 인수가 같거나 유사하다면 어댑터 작업을 쉽게 할 수 있다.
  • 클래스(Class) 어댑터를 사용함으로써 어댑티(Adaptee)의 동작을 오버라이딩할 수 있다. 하지만 이런 방식은 어댑티와 다르게 동작하는 어댑터에서는 사용하지 말아야 한다. 결함을 수정하는 일이 쉽지 않다.
  • 오브젝트(Object) 어댑터를 사용하면 서브클래스 중 하나에서 어댑티(Adaptee) 객체를 변경할 수 있게 된다.

디자인 고려사항

  • 클래스(Class) 어댑터는 대상(Target) 클래스와 어댑티(Adaptee) 클래스가 구체 클래스라면 불가능하다. 그런 경우에 오브젝트(Object) 어댑터를 사용해야 한다. 또한 Java에서 private 상속은 사용할 수 없으므로 오브젝트(Object) 어댑터를 사용해야 한다.
  • 클래스(Class) 어댑터는 대상 인터페이스와 어댑티(Adaptee) 양쪽에 나타낼 수 있으므로 양방향(two way) 어댑터라고도 한다. 어댑티(Adaptee) 객체가 있는 곳 뿐만 아니라 대상 인터페이스가 있는 곳에서 어댑터 객체를 사용할 수 있다.

위험요소

  • 어댑터를 확장하기 위해 대상 인터페이스와 어댑티 클래스를 사용하여 Java에서는 클래스(Class) 어댑터를 만들 수 있다. 하지만 코드 일부에서 관련없는 메소드를 노출하여 오염시키는 객체를 만들어 낸다. 클래스 어댑터를 사용하지 마라.
  • 간단한 인터페이스 변환작업을 하는 어댑터에서 많은 일을 할 수도 있다. 하지만 이것으로 인해 어댑터가 어댑티 객체와 다른 동작을 하는 결과를 낳을 수도 있다.
  • 특별한 단점은 많이 없다. 우리가 목적에 맞게 인터페이스 변환을 간단하게 유지만 하면 괜찮다.



반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유