CodeGym /Java Blog /무작위의 /어댑터 디자인 패턴은 어떤 문제를 해결합니까?
John Squirrels
레벨 41
San Francisco

어댑터 디자인 패턴은 어떤 문제를 해결합니까?

무작위의 그룹에 게시되었습니다
함께 작동해야 하는 호환되지 않는 구성 요소로 인해 소프트웨어 개발이 더 어려워집니다. 예를 들어 이전 버전의 Java로 작성된 이전 플랫폼과 새 라이브러리를 통합해야 하는 경우 호환되지 않는 개체 또는 호환되지 않는 인터페이스가 발생할 수 있습니다. 어댑터 디자인 패턴은 어떤 문제를 해결합니까?  - 1이 경우 어떻게 해야 합니까? 코드를 다시 작성하시겠습니까? 시스템을 분석하는 데 많은 시간이 걸리거나 애플리케이션의 내부 논리가 위반되기 때문에 그렇게 할 수 없습니다. 이 문제를 해결하기 위해 어댑터 패턴을 만들었습니다. 호환되지 않는 인터페이스가 있는 개체가 함께 작동하도록 도와줍니다. 그것을 사용하는 방법을 보자!

문제에 대한 추가 정보

먼저 이전 시스템의 동작을 시뮬레이션합니다. 그것이 직장이나 학교에 지각한 것에 대한 변명을 만들어낸다고 가정해 봅시다. 이를 위해 , 및 메소드 Excuse가 있는 인터페이스 가 있습니다 . generateExcuse()likeExcuse()dislikeExcuse()

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
WorkExcuse클래스는 다음 인터페이스를 구현합니다.

public class WorkExcuse implements Excuse {
   private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
예제를 테스트해 보겠습니다.

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
산출:

"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
이제 변명 생성 서비스를 시작하고 통계를 수집했으며 대부분의 사용자가 대학생임을 알게 되었다고 상상해 보십시오. 이 그룹에 더 나은 서비스를 제공하기 위해 다른 개발자에게 특히 대학생을 위한 변명을 생성하는 시스템을 만들도록 요청했습니다. 개발팀은 시장 조사를 수행하고, 변명에 순위를 매기고, 일부 인공 지능을 연결하고, 교통 정보, 날씨 정보 등과 서비스를 통합했습니다. 이제 대학생을 위한 변명을 생성하기 위한 라이브러리가 있지만 인터페이스가 다릅니다 StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
generateExcuse이 인터페이스에는 변명을 생성하는 와 dislikeExcuse변명이 나중에 다시 나타나지 않도록 방지하는 두 가지 방법이 있습니다 . 타사 라이브러리는 편집할 수 없습니다. 즉, 해당 소스 코드를 변경할 수 없습니다. 이제 우리가 가진 것은 인터페이스를 구현하는 두 개의 클래스가 있는 시스템 과 인터페이스를 구현하는 클래스가 Excuse있는 라이브러리 입니다 . SuperStudentExcuseStudentExcuse

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
코드는 변경할 수 없습니다. 현재 클래스 계층 구조는 다음과 같습니다. 어댑터 디자인 패턴은 어떤 문제를 해결합니까?  - 2이 버전의 시스템은 Excuse 인터페이스에서만 작동합니다. 코드를 다시 작성할 수 없습니다. 대규모 애플리케이션에서 이러한 변경을 수행하면 프로세스가 오래 걸리거나 애플리케이션의 논리가 중단될 수 있습니다. 기본 인터페이스를 도입하고 계층 구조를 확장할 수 있습니다. 어댑터 디자인 패턴은 어떤 문제를 해결합니까?  - 삼이렇게 하려면 인터페이스의 이름을 바꿔야 합니다 Excuse. 그러나 추가 계층 구조는 심각한 응용 프로그램에서는 바람직하지 않습니다. 공통 루트 요소를 도입하면 아키텍처가 손상됩니다. 최소한의 손실로 새 기능과 이전 기능을 모두 사용할 수 있도록 하는 중간 클래스를 구현해야 합니다. 즉, 어댑터가 필요합니다 .

어댑터 패턴의 원리

어댑터는 한 개체의 메서드 호출을 다른 개체에서 이해할 수 있도록 하는 중간 개체입니다. 예제에 대한 어댑터를 구현하고 Middleware. 어댑터는 개체 중 하나와 호환되는 인터페이스를 구현해야 합니다. 그대로 두십시오 Excuse. 이렇게 하면 Middleware첫 번째 개체의 메서드를 호출할 수 있습니다. Middleware호출을 수신하고 호환 가능한 방식으로 두 번째 개체에 전달합니다. 다음은 및 메소드를 Middleware사용한 구현 입니다 . generateExcusedislikeExcuse

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
테스트(클라이언트 코드에서):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
산출:

An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
현재 기상 조건, 교통 체증 또는 대중 교통 일정 지연에 적합한 놀라운 변명입니다. 이 generateExcuse메서드는 추가 변경 없이 단순히 호출을 다른 개체에 전달합니다. 그 dislikeExcuse방법은 우리가 먼저 변명을 블랙리스트에 올리도록 요구했습니다. 중간 데이터 처리를 수행할 수 있는 기능은 사람들이 어댑터 패턴을 좋아하는 이유입니다. likeExcuse그러나 인터페이스 의 일부이지만 인터페이스 Excuse의 일부가 아닌 메서드 는 어떻습니까 StudentExcuse? 새 기능은 이 작업을 지원하지 않습니다. 이 상황을 위해 발명 UnsupportedOperationException되었습니다. 요청된 작업이 지원되지 않으면 throw됩니다. 그것을 사용하자. Middleware클래스의 새 구현은 다음과 같습니다.

public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
언뜻 보기에 이 솔루션은 그다지 좋아 보이지 않지만 기능을 모방하면 상황이 복잡해질 수 있습니다. 클라이언트가 주의를 기울이고 어댑터가 잘 문서화되어 있으면 이러한 솔루션을 사용할 수 있습니다.

어댑터를 사용하는 경우

  1. 타사 클래스를 사용해야 하지만 해당 인터페이스가 기본 애플리케이션과 호환되지 않는 경우. 위의 예는 대상 개체가 이해할 수 있는 형식으로 호출을 래핑하는 어댑터 개체를 만드는 방법을 보여줍니다.

  2. 여러 기존 하위 클래스에 몇 가지 공통 기능이 필요한 경우. 추가 하위 클래스를 만드는 대신(코드 중복으로 이어질 수 있음) 어댑터를 사용하는 것이 좋습니다.

장점과 단점

이점: 어댑터는 한 개체에서 다른 개체로 요청을 처리하는 세부 정보를 클라이언트로부터 숨깁니다. 클라이언트 코드는 데이터 형식 지정이나 대상 메서드에 대한 호출 처리에 대해 생각하지 않습니다. 너무 복잡하고 프로그래머가 게을러요 :) 단점: 추가 클래스로 인해 프로젝트의 코드 베이스가 복잡합니다. 호환되지 않는 인터페이스가 많은 경우 추가 클래스의 수를 관리할 수 없게 될 수 있습니다.

어댑터를 파사드 또는 데코레이터와 혼동하지 마십시오.

피상적인 검사만으로는 어댑터가 파사드 및 데코레이터 패턴과 혼동될 수 있습니다. 어댑터와 파사드의 차이점은 파사드가 새로운 인터페이스를 도입하고 전체 하위 시스템을 래핑한다는 것입니다. 그리고 데코레이터는 어댑터와 달리 인터페이스가 아닌 개체 자체를 변경합니다.

단계별 알고리즘

  1. 먼저 이 패턴으로 해결할 수 있는 문제가 있는지 확인하십시오.

  2. 호환되지 않는 개체와 간접적으로 상호 작용하는 데 사용할 클라이언트 인터페이스를 정의합니다.

  3. 어댑터 클래스가 이전 단계에서 정의된 인터페이스를 상속하도록 합니다.

  4. 어댑터 클래스에서 어댑터 개체에 대한 참조를 저장할 필드를 만듭니다. 이 참조는 생성자에게 전달됩니다.

  5. 어댑터에서 모든 클라이언트 인터페이스 메소드를 구현하십시오. 메소드는 다음을 수행할 수 있습니다.

    • 변경하지 않고 통화 전달

    • 데이터 수정 또는 보완, 대상 메서드 호출 횟수 증감 등

    • 극단적인 경우 특정 메서드가 계속 호환되지 않으면 UnsupportedOperationException이 발생합니다. 지원되지 않는 작업은 엄격하게 문서화해야 합니다.

  6. 애플리케이션이 클라이언트 인터페이스를 통해서만 어댑터 클래스를 사용하는 경우(위의 예에서처럼) 나중에 어댑터를 쉽게 확장할 수 있습니다.

물론 이 디자인 패턴이 모든 병에 대한 만병통치약은 아니지만 인터페이스가 다른 개체 간의 비호환성 문제를 우아하게 해결하는 데 도움이 될 수 있습니다. 기본 패턴을 아는 개발자는 알고리즘 작성 방법만 아는 개발자보다 몇 단계 앞서 있습니다. 왜냐하면 디자인 패턴은 심각한 응용 프로그램을 만드는 데 필요하기 때문입니다. 코드 재사용이 그리 어렵지 않고 유지 관리가 즐거워집니다. 오늘은 그게 다야! 하지만 우리는 곧 다양한 디자인 패턴을 계속 알아갈 것입니다 :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION