CodeGym /Java Blog /Toto sisi /適配器設計模式解決了什麼問題?
John Squirrels
等級 41
San Francisco

適配器設計模式解決了什麼問題?

在 Toto sisi 群組發布
需要協同工作的不兼容組件使軟件開髮變得更加困難。例如,如果您需要將新庫與用早期 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接口的類的系統,以及一個包含一個SuperStudentExcuse實現StudentExcuse接口的類的庫:

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 接口。您無法重寫代碼:在大型應用程序中,進行此類更改可能會成為一個漫長的過程或破壞應用程序的邏輯。我們可以引入一個基本接口並擴展層次結構: 適配器設計模式解決了什麼問題? - 3為此,我們必須重命名該Excuse接口。但是額外的層次結構在嚴肅的應用程序中是不可取的:引入一個公共根元素會破壞體系結構。您應該實現一個中間類,讓我們以最小的損失同時使用新舊功能。簡而言之,您需要一個適配器

適配器模式背後的原理

適配器是一種中間對象,它允許一個對象的方法調用被另一個對象理解。讓我們為示例實現一個適配器並將其命名為Middleware. 我們的適配器必須實現與其中一個對象兼容的接口。順其自然吧Excuse。這允許Middleware調用第一個對象的方法。 Middleware接收調用並以兼容的方式將它們轉發給第二個對象。這是Middleware使用generateExcuseanddislikeExcuse方法的實現:

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為這種情況而發明的。如果不支持請求的操作,則拋出該異常。讓我們使用它。這是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