擴充卡模式
說到擴充卡,我們現實生活中到處都是擴充卡,其中最容易被大家提起的就是電源擴充卡。
比如牆上隻有一個三孔的插座,而你的手機充電器是兩孔的,這個時候你需要一個,一邊是三腳,一邊提供兩個孔的擴充卡,來做一次插座接口轉換。
實際上,我們常使用的插線闆就有這個功能,是以某些場景下它就是擴充卡。
不相容的API更新
那麼回到面向對象中來,假設現在你手上已有一個軟體系統,內建了廠商的類;現在需要更新廠商到新的類,但是廠商新類提供的API新舊不相容,如果你不想修改已有的代碼,來解決掉這個更新問題?
審視下目前系統有些什麼
首先有廠商相關的類
/**
* 原始的廠商API
*/
public interface OldVendorApi {
void doSomething();
}
/**
* 舊廠商實作
*/
public class OldVendorApiImplV1 implements OldVendorApi {
@Override
public void doSomething() {
System.out.println("執行舊廠商代碼....");
}
}
有使用廠商類的代碼
/**
* 使用廠商類的用戶端代碼
*/
public class YourSystemClient {
private OldVendorApi vendorAPI;
public YourSystemClient(OldVendorApi vendorAPI) {
this.vendorAPI = vendorAPI;
}
public void callVendorApi(){
System.out.println("調用廠商類api....");
vendorAPI.doSomething();
}
}
能夠正常跑
/**
* 測試
*/
public class VendorMainV1 {
public static void main(String[] args) {
YourSystemClient systemClient = new YourSystemClient(new OldVendorApiImplV1());
systemClient.callVendorApi();
}
}
好了,如果現在要更新不相容的新廠商接口(實際情況下,這種廠商還是很少的),新的api和實作如下
/**
* 新的廠商API
*/
public interface NewVendorApiV2 {
void doSomething2();
}
/**
* 新廠商實作
*/
public class NewVendorApiImplV2 implements NewVendorApiV2 {
@Override
public void doSomething2() {
System.out.println("執行新的廠商代碼....");
}
}
我們不修改YourSystemClient類的情況下,可以使用擴充卡來完成這個更新。
新增擴充卡
/**
* 舊廠商接口擴充卡,實作舊的接口,實際功能調用到新的API上
*/
public class OldVendorApiAdapter implements OldVendorApi{
private NewVendorApiV2 newVendorApi;
public OldVendorApiAdapter(NewVendorApiV2 newVendorApi) {
this.newVendorApi = newVendorApi;
}
@Override
public void doSomething() {
newVendorApi.doSomething2();
}
}
讓我們的用戶端依賴新的擴充卡,代碼如下
/**
* 測試
*/
public class VendorMainV2 {
public static void main(String[] args) {
OldVendorApiAdapter oldVendorApiAdapter = new OldVendorApiAdapter(new NewVendorApiImplV2());
YourSystemClient systemClient = new YourSystemClient(oldVendorApiAdapter);
systemClient.callVendorApi();
}
}
這樣,通過傳入擴充卡類,就實作了,不修改代碼,通過增加代碼來實作更新。
按照慣例,看下UML圖
火雞僞裝鴨子
再看一個示例,看看如何讓火雞來僞裝成一個鴨子,混到鴨子中去的。
首先我們先吧鴨子接口和實作,火雞接口和實作提供出來。
/**
* 鴨子接口
*/
public interface Duck {
void quack();
void fly();
}
/**
* 綠頭鴨
*/
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("呱呱呱的叫");
}
@Override
public void fly() {
System.out.println("飛30米...");
}
}
/**
* 火雞接口
*/
public interface Turkey {
void gobble();
void fly();
}
/**
* 野生火雞
*/
public class WildTurkey implements Turkey{
@Override
public void gobble() {
System.out.println("咕咕的叫");
}
@Override
public void fly() {
System.out.println("飛了10米");
}
}
接下來,使用一個擴充卡,通過它來将火雞僞裝成鴨子
/**
* 火雞擴充卡-将火雞适配成鴨子
*/
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public 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圖
定義
适配者模式将一個類的接口,裝換為客戶希望的另外一個接口,讓原本不相容的類可以合作無間。
我們用代碼來實作一波
目标接口(客戶希望的接口)
/**
* 目标接口
*/
public interface Target {
void request();
}
不能夠直接相容的被适配者
/**
* 被适配者
*/
public class Adaptee {
public void specialRequest(){
System.out.println("被适配者具體工作....");
}
}
做接口轉換的擴充卡類
/**
* 擴充卡,實作目标接口,将請求轉發給被适配者
*/
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public 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();
}
}
可以看到,通過擴充卡類的接口轉換和轉發,讓客戶類通過目标接口,使用了原本不能相容的被适配者類的能力。
看看UML圖。
我們前面學到了裝飾者模式,他們都是通過加一層包裝來實作客戶也具體工作類之間的解耦。
他們的差別是什麼?
适配者模式是通過包裝來提供接口轉換,而裝飾者通過包裝來動态的提供新的能力,他們的意圖不同。
擴充示例
https://www.c-sharpcorner.com/UploadFile/b7dc95/adapter-design-pattern-demystified/
這是一個隻支援USB接口的筆記本電腦,要使用老的PS/2接口滑鼠時,使用擴充卡的例子。
外觀模式
你已經知道擴充卡模式是如何将一個接口轉換為成另一個符合客戶預期的接口,達到相容的目的;
另一個裝換接口的模式,其目的是為了簡化接口,這個模式叫外觀模式,它将一個或者數個複雜的接口隐藏在背後,留出幹淨美好的外觀。
裝房子
通過前面的項目中,攢了些錢,在加上父母贊助和向朋友借錢,買了一套房子,現在交房了準備裝修,通過向朋友了解了下,有兩種方式。
一種方式是自己買材料,自己聯系勞工(泥水工、木工、電工),自己檢驗,所有的事情親力親為,但相對省錢。
另一種方式是,通過裝修公司,所有的事情整包給裝修公司,你隻需要交錢、驗收就好了(當然實際情況可沒有這麼簡單,中間還是有很多溝通和互動的)
那麼我們來分别用代碼實作看看。
首先,将裝修中要打交道的角色建立出來。
/**
* 地磚商人
*/
public class 地磚商人 {
public void 購買地磚(){
System.out.println("從地磚商人購買地磚");
}
}
/**
* 水泥商人
*/
public class 水泥商人 {
public void 購買水泥(){
System.out.println("從水泥商人購買水泥");
}
}
/**
* 石材商人
*/
public class 石材商人 {
public void 購買石材(){
System.out.println("從石材商人購買石材");
}
}
/**
* 泥水工
*/
public class 泥水工 {
public void 鋪地磚(){
System.out.println("泥水工鋪地磚");
}
}
/**
* 木工
*/
public class 木工 {
public void 做櫃子(){
System.out.println("木工做櫃子");
}
}
/**
* 電工
*/
public class 電工 {
public void 鋪電線(){
System.out.println("電工鋪電線");
}
}
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的做法,通過裝修公司來裝修新房
/**
* 代替裝修業主和各個角色打交道,髒活累活都有我來做吧。
*/
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版的實作就用到了外觀模式,也叫門面模式,外觀模式提供了一個統一的接口,用來通路子系統中的一群接口,簡化子系統的使用。
當然,你可以繞過這個簡單的外觀,而直接使用子系統的接口,做精細化控制。
代碼來實作一波定義。
/**
* 子系統A
*/
public class SubsystemA {
public void doSpecial(){
System.out.println("子系統A執行特殊的方法....");
}
}
/**
* 子系統B
*/
public class SubsystemB {
public void doSpecial(){
System.out.println("子系統B執行特殊的方法....");
}
}
/**
* 子系統C
*/
public class SubsystemC {
public void doSpecial(){
System.out.println("子系統C執行特殊的方法....");
}
}
接下來,用一個門面來聚合子系統的能力,提供聚合能力
/**
* 門面,提供簡單幹淨的對外接口
*/
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圖
擴充示例
為了加深了解,我們來看下一個電商庫存的例子。
同樣,先把子系統建構起來,就是實際幹活的如庫存管理、校驗管理、費用計算、支付、物流這些子系統。
各個子系統,貼出兩個子系統的代碼,其他子系統類似。
/**
* 庫存接口
*/
public interface IInventory {
void update(int productId);
}
/**
* 庫存管理
*/
public class InventoryManager implements IInventory {
@Override
public void update(int productId) {
String msg = "Product# " + productId +
" is subtracted from store's inventory";
System.out.println(msg);
}
}
/**
* 訂單校驗接口
*/
public interface IOrderVerify {
boolean verifyShippingAddress(int pincode);
}
/**
* 訂單檢驗管理
*/
public class OrderVerificationManager implements IOrderVerify {
@Override
public boolean verifyShippingAddress(int pincode) {
System.out.println(
"The product can be shipped to the pincode "
+ pincode);
return true;
}
}
如果沒使用外觀時,客戶直接使用子系統,代碼會長成下面這個樣子
/**
* 沒有使用外觀時的客戶長這個樣子
*/
public class NoFacadeMain {
public static void main(String[] args) {
// Creating the Order/Product details
OrderDetails 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 details
OrderDetails orderDetails = new OrderDetails("Java Design Pattern book",
"Simplified book on design patterns in Java",
500, 10, "Street No 1", "Educational Area", 1212,
"8811123456");
// Using Facade
OnlineShoppingFacade 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