适配器和外观模式
適配器模式
說到適配器,我們現(xiàn)實生活中到處都是適配器,其中最容易被大家提起的就是電源適配器。
比如墻上只有一個三孔的插座,而你的手機充電器是兩孔的,這個時候你需要一個,一邊是三腳,一邊提供兩個孔的適配器,來做一次插座接口轉(zhuǎn)換。
實際上,我們常使用的插線板就有這個功能,所以某些場景下它就是適配器。
不兼容的API升級
那么回到面向?qū)ο笾衼?#xff0c;假設(shè)現(xiàn)在你手上已有一個軟件系統(tǒng),集成了廠商的類;現(xiàn)在需要升級廠商到新的類,但是廠商新類提供的API新舊不兼容,如果你不想修改已有的代碼,來解決掉這個升級問題?
審視下當(dāng)前系統(tǒng)有些什么
首先有廠商相關(guān)的類
有使用廠商類的代碼
/*** 使用廠商類的客戶端代碼*/ public class YourSystemClient {private OldVendorApi vendorAPI;public YourSystemClient(OldVendorApi vendorAPI) {this.vendorAPI = vendorAPI;}public void callVendorApi(){System.out.println("調(diào)用廠商類api....");vendorAPI.doSomething();} }能夠正常跑
/*** 測試*/ public class VendorMainV1 {public static void main(String[] args) {YourSystemClient systemClient = new YourSystemClient(new OldVendorApiImplV1());systemClient.callVendorApi();} }好了,如果現(xiàn)在要升級不兼容的新廠商接口(實際情況下,這種廠商還是很少的),新的api和實現(xiàn)如下
/*** 新的廠商API*/ public interface NewVendorApiV2 {void doSomething2();}/*** 新廠商實現(xiàn)*/ public class NewVendorApiImplV2 implements NewVendorApiV2 {@Overridepublic void doSomething2() {System.out.println("執(zhí)行新的廠商代碼....");} }我們不修改YourSystemClient類的情況下,可以使用適配器來完成這個升級。
新增適配器
讓我們的客戶端依賴新的適配器,代碼如下
/*** 測試*/ public class VendorMainV2 {public static void main(String[] args) {OldVendorApiAdapter oldVendorApiAdapter = new OldVendorApiAdapter(new NewVendorApiImplV2());YourSystemClient systemClient = new YourSystemClient(oldVendorApiAdapter);systemClient.callVendorApi();} }這樣,通過傳入適配器類,就實現(xiàn)了,不修改代碼,通過增加代碼來實現(xiàn)升級。
按照慣例,看下UML圖
火雞偽裝鴨子
再看一個示例,看看如何讓火雞來偽裝成一個鴨子,混到鴨子中去的。
首先我們先吧鴨子接口和實現(xiàn),火雞接口和實現(xiàn)提供出來。
接下來,使用一個適配器,通過它來將火雞偽裝成鴨子
/*** 火雞適配器-將火雞適配成鴨子*/ public class TurkeyAdapter implements Duck {Turkey turkey;public TurkeyAdapter(Turkey turkey) {this.turkey = turkey;}@Overridepublic void quack() {turkey.gobble();}@Overridepublic void fly() {for (int i = 0; i < 3; i++) {turkey.fly();}} }測試偽裝
/*** 測試*/ public class DuckMain {public static void main(String[] args) {Duck duck = new MallardDuck();duck.quack();duck.fly();System.out.println();duck = new TurkeyAdapter(new WildTurkey());duck.quack();duck.fly();} }看下UML圖
定義
適配者模式將一個類的接口,裝換為客戶希望的另外一個接口,讓原本不兼容的類可以合作無間。
我們用代碼來實現(xiàn)一波
目標(biāo)接口(客戶希望的接口)
不能夠直接兼容的被適配者
/*** 被適配者*/ public class Adaptee {public void specialRequest(){System.out.println("被適配者具體工作....");} }做接口轉(zhuǎn)換的適配器類
/*** 適配器,實現(xiàn)目標(biāo)接口,將請求轉(zhuǎn)發(fā)給被適配者*/ public class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {adaptee.specialRequest();} }客戶端類
/*** 客戶*/ public class Client {private Target target;public Client(Target target) {this.target = target;}public void doSomething(){target.request();} }測試
/*** 測試*/ public class AdapterMain {public static void main(String[] args) {Adapter adapter = new Adapter(new Adaptee());Client client = new Client(adapter);client.doSomething();} }可以看到,通過適配器類的接口轉(zhuǎn)換和轉(zhuǎn)發(fā),讓客戶類通過目標(biāo)接口,使用了原本不能兼容的被適配者類的能力。
看看UML圖。
我們前面學(xué)到了裝飾者模式,他們都是通過加一層包裝來實現(xiàn)客戶也具體工作類之間的解耦。
他們的區(qū)別是什么?
適配者模式是通過包裝來提供接口轉(zhuǎn)換,而裝飾者通過包裝來動態(tài)的提供新的能力,他們的意圖不同。
擴展示例
https://www.c-sharpcorner.com/UploadFile/b7dc95/adapter-design-pattern-demystified/
這是一個只支持USB接口的筆記本電腦,要使用老的PS/2接口鼠標(biāo)時,使用適配器的例子。
外觀模式
你已經(jīng)知道適配器模式是如何將一個接口轉(zhuǎn)換為成另一個符合客戶預(yù)期的接口,達到兼容的目的;
另一個裝換接口的模式,其目的是為了簡化接口,這個模式叫外觀模式,它將一個或者數(shù)個復(fù)雜的接口隱藏在背后,留出干凈美好的外觀。
裝房子
通過前面的項目中,攢了些錢,在加上父母贊助和向朋友借錢,買了一套房子,現(xiàn)在交房了準(zhǔn)備裝修,通過向朋友了解了下,有兩種方式。
一種方式是自己買材料,自己聯(lián)系工人(泥水工、木工、電工),自己檢驗,所有的事情親力親為,但相對省錢。
另一種方式是,通過裝修公司,所有的事情整包給裝修公司,你只需要交錢、驗收就好了(當(dāng)然實際情況可沒有這么簡單,中間還是有很多溝通和交互的)
那么我們來分別用代碼實現(xiàn)看看。
首先,將裝修中要打交道的角色創(chuàng)建出來。
V1版
V1版使用親力親為的這種方式。
/*** 親力親為的新房主人,要和所有人打交道,好累。*/ public class 新房主人V1 {public void 裝修房子(){地磚商人 地磚商人 = new 地磚商人();地磚商人.購買地磚();水泥商人 水泥商人 = new 水泥商人();水泥商人.購買水泥();石材商人 石材商人 = new 石材商人();石材商人.購買石材();泥水工 泥水工 = new 泥水工();泥水工.鋪地磚();木工 木工 = new 木工();木工.做柜子();電工 電工 = new 電工();電工.鋪電線();} }測試走起
/*** 測試*/ public class HouseDecorateMainV1 {public static void main(String[] args) {新房主人V1 新房主人 = new 新房主人V1();新房主人.裝修房子();} }可以看出,主人為了省錢還是很拼的。
V2版
接下來看看V2的做法,通過裝修公司來裝修新房
/*** 代替裝修業(yè)主和各個角色打交道,臟活累活都有我來做吧。*/ public class 裝修公司 {public void 裝修新房(){地磚商人 地磚商人 = new 地磚商人();地磚商人.購買地磚();水泥商人 水泥商人 = new 水泥商人();水泥商人.購買水泥();石材商人 石材商人 = new 石材商人();石材商人.購買石材();泥水工 泥水工 = new 泥水工();泥水工.鋪地磚();木工 木工 = new 木工();木工.做柜子();電工 電工 = new 電工();電工.鋪電線();} }V2版的新房主人就之和裝修公司打交道,世界都變得美好起來了。
/*** 親力親為的新房主人,要和所有人打交道,好累。*/ public class 新房主人V2 {public void 裝修房子(){裝修公司 兄弟裝修公司 = new 裝修公司();兄弟裝修公司.裝修新房();} }測試
/*** 測試*/ public class HouseDecorateMainV2 {public static void main(String[] args) {新房主人V2 新房主人 = new 新房主人V2();新房主人.裝修房子();} }可以看出,代碼是一樣,不過V2中在新房主人和各個商人/工人中間加入一層,這一層聚合了和各個角色交互,對于裝房的人來說,簡化了裝房的過程。
看下V2的UML圖
定義
V2版的實現(xiàn)就用到了外觀模式,也叫門面模式,外觀模式提供了一個統(tǒng)一的接口,用來訪問子系統(tǒng)中的一群接口,簡化子系統(tǒng)的使用。
當(dāng)然,你可以繞過這個簡單的外觀,而直接使用子系統(tǒng)的接口,做精細化控制。
代碼來實現(xiàn)一波定義。
/*** 子系統(tǒng)A*/ public class SubsystemA {public void doSpecial(){System.out.println("子系統(tǒng)A執(zhí)行特殊的方法....");} }/*** 子系統(tǒng)B*/ public class SubsystemB {public void doSpecial(){System.out.println("子系統(tǒng)B執(zhí)行特殊的方法....");} }/*** 子系統(tǒng)C*/ public class SubsystemC {public void doSpecial(){System.out.println("子系統(tǒng)C執(zhí)行特殊的方法....");} }接下來,用一個門面來聚合子系統(tǒng)的能力,提供聚合能力
/*** 門面,提供簡單干凈的對外接口*/ public class SystemFacade {public void doSomeSpecialThing(){SubsystemA subsystemA = new SubsystemA();subsystemA.doSpecial();SubsystemB subsystemB = new SubsystemB();subsystemB.doSpecial();SubsystemC subsystemC = new SubsystemC();subsystemC.doSpecial();} }這樣,客戶就可以非常容易的使用門面來獲取聚合的能力了
/*** 使用門面的客戶*/ public class Client {public void doSomeThing(){SystemFacade systemFacade = new SystemFacade();systemFacade.doSomeSpecialThing();}public static void main(String[] args) {new Client().doSomeThing();} }觀察UML圖
擴展示例
為了加深理解,我們來看下一個電商庫存的例子。
同樣,先把子系統(tǒng)構(gòu)建起來,就是實際干活的如庫存管理、校驗管理、費用計算、支付、物流這些子系統(tǒng)。
各個子系統(tǒng),貼出兩個子系統(tǒng)的代碼,其他子系統(tǒng)類似。
如果沒使用外觀時,客戶直接使用子系統(tǒng),代碼會長成下面這個樣子
/*** 沒有使用外觀時的客戶長這個樣子*/ public class NoFacadeMain {public static void main(String[] args) {// Creating the Order/Product detailsOrderDetails orderDetails = new OrderDetails("Java Design Pattern book","Simplified book on design patterns in Java",500, 10, "Street No 1", "Educational Area", 1212,"8811123456");// Updating the inventory.IInventory inventory = new InventoryManager();inventory.update(orderDetails.getProductNo());// verifying various details for the order such as the shipping address.IOrderVerify orderVerify = new OrderVerificationManager();orderVerify.verifyShippingAddress(orderDetails.getPinCode());// Calculating the final cost after applying various discounts.ICosting costManager = new CostManager();orderDetails.setPrice(costManager.applyDiscount(orderDetails.getPrice(),orderDetails.getDiscountPercent()));// Going through various steps if payment gateway like card verification,// charging from the card.IPaymentGateway paymentGateway = new PaymentGatewayManager();paymentGateway.verifyCardDetails(orderDetails.getCardNo());paymentGateway.processPayment(orderDetails.getCardNo(), orderDetails.getPrice());// Completing the order by providing logistics.ILogistics logistics = new LogisticsManager();String shippingAddress = String.format("%s, %s - %d",orderDetails.getAddressLine1(),orderDetails.getAddressLine2(),orderDetails.getPinCode());logistics.shipProducts(orderDetails.getProductName(), shippingAddress);} }但是如果引入了外觀后,客戶會長成:
/*** 使用外觀后的客戶長這個樣子*/ public class FacadeMain {public static void main(String[] args) {// Creating the Order/Product detailsOrderDetails orderDetails = new OrderDetails("Java Design Pattern book","Simplified book on design patterns in Java",500, 10, "Street No 1", "Educational Area", 1212,"8811123456");// Using FacadeOnlineShoppingFacade facade = new OnlineShoppingFacade();facade.finalizeOrder(orderDetails);} }其中的外觀:
/*** 購物外觀*/ public class OnlineShoppingFacade {IInventory inventory = new InventoryManager();IOrderVerify orderVerify = new OrderVerificationManager();ICosting costManager = new CostManager();IPaymentGateway paymentGateway = new PaymentGatewayManager();ILogistics logistics = new LogisticsManager();public void finalizeOrder(OrderDetails orderDetails) {inventory.update(orderDetails.getProductNo());orderVerify.verifyShippingAddress(orderDetails.getPinCode());orderDetails.setPrice(costManager.applyDiscount(orderDetails.getPrice(),orderDetails.getDiscountPercent()));paymentGateway.verifyCardDetails(orderDetails.getCardNo());paymentGateway.processPayment(orderDetails.getCardNo(), orderDetails.getPrice());String shippingAddress = String.format("%s, %s - %d",orderDetails.getAddressLine1(),orderDetails.getAddressLine2(),orderDetails.getPinCode());logistics.shipProducts(orderDetails.getCardNo(), shippingAddress);} }該示例代碼參考: https://www.codeproject.com/Articles/767154/Facade-Design-Pattern-Csharp
源碼
https://gitee.com/cq-laozhou/design-pattern
總結(jié)
- 上一篇: 第5章 模块与函数——笔记八
- 下一篇: 运用大数据服务环境质量改善