軟體開發通常因相互協作的組件之間的不相容性而變得複雜。例如,如果您需要將新程式庫與用早期版本的 Java 編寫的舊平台集成,您可能會遇到物件不相容的情況,或者更準確地說,介面不相容的情況。這種情況該怎麼辦?重寫程式碼?但這是不可能的:分析系統將花費大量時間,或工作的內部邏輯將被破壞。為了解決這個問題,他們提出了適配器模式,它可以幫助具有不相容介面的物件一起工作。讓我們看看如何使用它!
有關問題的更多詳細信息
首先,讓我們模擬舊系統的行為。假設它會產生上班或上學遲到的原因。為此,我們有一個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[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
"искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все How в тумане.",
"предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};
@Override
public String generateExcuse() { // Случайно выбираем отговорку из массива
String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
sorryOptions[(int) Math.round(Math.random() + 1)];
return result;
}
@Override
public void likeExcuse(String excuse) {
// Дублируем элемент в массиве, чтобы шанс его выпадения был выше
}
@Override
public void dislikeExcuse(String excuse) {
// Удаляем элемент из массива
}
}
讓我們測試一下這個例子:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
結論:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Прошу меня извинить за непрофессиональное поведение.
現在,我們假設您推出了該服務,收集了統計數據,並注意到大多數服務使用者都是大學生。為了改進它以滿足該組的需求,您專門從另一位開發人員那裡為她訂購了一個藉口生成系統。開發團隊進行了研究、編制評級、連接人工智慧,並添加了與交通擁堵、天氣等的整合。現在你有一個為學生生成藉口的庫,但與其互動的介面不同 - StudentExcuse
:
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
此介面有兩個方法:generateExcuse
,產生藉口,和dislikeExcuse
,阻止藉口,使其不再出現。第三方程式庫已關閉編輯 - 您無法變更其原始程式碼。因此,在您的系統中有兩個實作該介面的類Excuse
,以及一個包含SuperStudentExcuse
實作該介面的類別的函式庫StudentExcuse
:
public class SuperStudentExcuse implements StudentExcuse {
@Override
public String generateExcuse() {
// Логика нового функционала
return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта.";
}
@Override
public void dislikeExcuse(String excuse) {
// Добавляет причину в черный список
}
}
程式碼無法更改。目前的方案將如下所示: 該版本的系統僅適用於 Excuse 介面。您無法重寫程式碼:在大型應用程式中,此類變更可能需要很長時間或破壞應用程式邏輯。您可以建議引入主介面並增加層次結構: 為此,您需要重新命名介面Excuse
。但在嚴肅的應用程式中,額外的層次結構是不可取的:引入公共根元素會破壞架構。應該實作一個中間類,允許以最小的損失使用新舊功能。簡而言之,您需要一個適配器。
適配器模式如何運作
適配器是一種中間對象,它使一個對像上的方法呼叫可以被另一個對象理解。讓我們為我們的範例實作一個適配器並將其命名為Middleware
。我們的適配器必須實作與其中一個物件相容的介面。隨它走Excuse
。因此,Middleware
它可以呼叫第一個物件的方法。 Middleware
接收呼叫並將它們以相容的格式傳遞給第二個物件。Middleware
這就是methodsgenerateExcuse
和的方法的實作dislikeExcuse
:
public class Middleware implements Excuse { // 1. Middleware становится совместимым с an objectом WorkExcuse через интерфейс Excuse
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый an object
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
}
@Override
public void dislikeExcuse(String excuse) {
// Метод предварительно помещает отговорку в черный список БД,
// Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
}
// Метод likeExcuse появятся позже
}
測試(在客戶端程式碼中):
public class Test {
public static void main(String[] args) {
Excuse excuse = new WorkExcuse(); // Создаются an objectы классов,
StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
System.out.println("Обычная причина для работника:");
System.out.println(excuse.generateExcuse());
System.out.println("\n");
Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в an object-адаптер
System.out.println("Использование нового функционала с помощью адаптера:");
System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
}
}
結論:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности.
Использование нового функционала с помощью адаптера
這是一個令人難以置信的藉口,適合當前的天氣狀況、交通擁堵或公共交通時間表的中斷。該方法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("Метод likeExcuse не поддерживается в новом функционале");
}
@Override
public void dislikeExcuse(String excuse) {
// Метод обращается за дополнительной информацией к БД,
// Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
}
}
乍一看,這個解決方案似乎並不成功,但模擬功能可能會導致更複雜的情況。如果客戶細心且適配器有詳細記錄,則此解決方案是可以接受的。
何時使用適配器
-
如果需要使用第三方類,但其介面與主應用程式不相容。上面的範例顯示如何建立 shim 對象,該對像以目標對象可以理解的格式包裝呼叫。
-
當多個現有子類別必須具有共同功能時。最好使用適配器,而不是額外的子類別(它們的建立將導致程式碼重複)。
的優點和缺點
優點:適配器向客戶端隱藏從一個物件到另一個物件的處理請求的細節。客戶端程式碼不會考慮格式化資料或處理對目標方法的呼叫。它太複雜了,而且程式設計師很懶:) 缺點:專案的程式碼庫因額外的類別而變得複雜,如果存在大量不相容點,它們的數量可能會增長到無法控制的大小。不要與 Facade 和 Decorator 混淆
從表面上看,適配器可能會與外觀和裝飾器模式混淆。Adapter 和 Facade 之間的區別在於 Facade 引入了新的介面並包裝了整個子系統。嗯,與適配器不同,裝飾器更改物件本身,而不是介面。分步實現演算法
-
首先,確保存在該模式可以解決的問題。
-
定義一個客戶端接口,將使用另一個類別來代表該接口。
-
基於上一個步驟中定義的介面實作適配器類別。
-
在適配器類別中,建立一個儲存物件參考的欄位。該引用在建構函數中傳遞。
-
在適配器中實作所有客戶端介面方法。此方法可以:
-
不做任何修改即可轉接通話;
-
更改資料、增加/減少目標方法的呼叫次數、進一步擴展資料的組成等。
-
作為最後的手段,如果特定方法不相容,則拋出 UnsupportedOperationException,嚴格需要記錄該異常。
-
-
如果應用程式僅透過用戶端介面使用適配器(如上例所示),則將來可以輕鬆擴展適配器。
GO TO FULL VERSION