日常開發系統中通常需要對接多個系統,需要用到配接器模式,
例如:支付方式就涉及多個系統對接,
國際慣例,先引入概念,
配接器模式:

提到配接器自然就能想到手機用的電源配接器,
他的作用就是將220V交流電轉換成手機使用的5V直流電,
配接器作用:將一個介面轉換成另外一個介面,已符合客戶的期望,

軟體系統中,比如一期我們使用了阿里云的sdk包的一些功能介面,
但是二期我想換騰訊云sdk相同的功能,但是他們相同的功能,介面引數確不同,
我們不想按照騰訊云的介面修改我們的業務代碼,畢竟業務邏輯已經經過反復測驗驗證了,可以把騰訊云的介面包裝起來,實作一個一期的介面,這個作業叫【適配】,
在例如:
在軟體系統中,你可能有很多種支付方式,微信支付,支付寶支付,各種銀行,
但是他們的支付介面肯定都是不一樣的,我不希望我新加一種支付方式就加去修改代碼,
此時就需要有一個統一支付的配接器服務幫我們屏蔽各個支付方式的不同,
我的業務服務只和統一支付互動,統一支付向業務系統提供統一介面,統一支付負責路由不同支付系統后臺,并屏蔽掉各系統差異,這個作業也叫【適配】
適配模式:

/** * 目標介面:提供5V電壓的一個介面 */ public interface V5Power { public int provideV5Power(); } /** * 被適配者、已有功能:家用220V交流電 */ public class V220Power { /** * 提供220V電壓 */ public int provideV220Power() { System.out.println("我提供220V交流電壓,"); return 220 ; } } /** * 配接器,有已有物件已有功能實作介面,把220V電壓變成5V */ public class V5PowerAdapter implements V5Power { /** * 組合的方式 */ private V220Power v220Power ; public V5PowerAdapter(V220Power v220Power) { this.v220Power = v220Power ; } @Override public int provideV5Power() { int power = v220Power.provideV220Power() ; //power經過各種操作-->5 System.out.println("配接器:我悄悄的適配了電壓,"); return 5 ; } }配接器
public class Mobile { // 使用目標介面功能 public void inputPower(V5Power power) { int provideV5Power = power.provideV5Power(); System.out.println("手機(客戶端):我需要5V電壓充電,現在是-->" + provideV5Power + "V"); } } //測驗 public class Test { public static void main(String[] args) { Mobile mobile = new Mobile(); V5Power v5Power = new V5PowerAdapter(new V220Power()) ; mobile.inputPower(v5Power); } }Test
定義:
將一個類的介面,轉換成客戶期望的另一個介面,配接器讓原本介面不兼容的類可以合作無間,
適配模式說的通俗點就是用一個已有的功能類,去實作一個介面,這樣該功能類,和原來的客戶端代碼都不需要改變,對于客戶端來說相當于換了一種實作方式,
好處就是讓客戶從實作中【解耦】,下次在換其他的功能類,我就再寫一個配接器,有點像策略模式中,我們換一個實作類一樣,
當然,我們配接器可以包裝很多被適配物件,即可以組合很多已有功能類,因為很多介面很復雜需要使用很多類,
同樣,配接器也可以不使用【組合】物件形式,而是使用【繼承】,
示例:
以前java集合類都實作了Enumeration列舉介面,可以遍歷集合中每個元素,而無需知道它們在集合內元素是如何被管理,
之后推出了新的Iterator迭代器介面,這個介面和列舉介面很像,都可以讓你遍歷集合中每個元素,
不同的是,迭代器還提供了洗掉元素的能力,
面對遺留代碼,這些代碼暴露出來的是列舉介面,他們是老版本的java只支持列舉,但是我們希望新的代碼中使用迭代器,只能用列舉實作一個迭代器,

public class EnumerationIterator implements Iterator{ Enumeration enum; public EnumerationIterator(Enumeration enum){ this.enum = enum; } public boolean hasNext(){ return enum.hasMoreElements(); } public Object next(){ return enum.nextElement(); } public void remove(){ throw new UnsupportedOperationException(); } } /* 列舉介面是只讀的,配接器無法實作一個有實際功能的remove()方法我們的實作方式并不完美,客戶必須小心潛在的例外,只要客戶足夠小心,并且在配接器的檔案中作出說明,這也算是一個合理的解決方案, */用列舉實作迭代器
外觀模式:

外觀模式:提供一個統一介面,用來訪問子系統中的一群介面,外觀定義了一個高層介面,讓子系統更容易使用,
例如:我們遙控器點擊下自動就打開幕布、打開投影機、打開音響、開始放電影,而不是一步一步的自己操作,
不僅僅是簡化了介面,也將客戶從組建匯總解耦了出來,
目的就是讓系統更加容易使用,也符合【最少知道】原則,因為客戶只有【外觀角色】一個朋友,
【最少知道】原則就是不要讓太多類耦合在一起,免得修改系統時候,牽一發而動全身,物件盡量“少交朋友”,之和自己有關的互動即可,
就物件而言,在該物件的方法內,我們只應該呼叫屬于以下范圍的方法:
1.該物件本身
2.被當做方法的引數而傳遞進來的物件
3.此方法所創建或實體化的任何物件
4.物件的任何組建,即屬性變數參考的物件
如果呼叫回傳物件的方法,相當于向另外一個物件的子部分發出請求,我們就多依賴了一個物件,
|
// 不應用【最少知道】原則 public float getTemp(){ Thermometer thermometer = station.getThermometer(); return thermometer.getTemperature(); // 我們從氣象站獲取了溫度計物件,然后從該物件獲取了溫度, } |
|
// 應用【最少知道】原則 public float getTemp(){ return station.getTemperature(); // 應該直接讓氣象站給我溫度,我不想依賴溫度計物件, } |
裝飾模式:

用面向物件的方式對飲料進行描述,這種描述方式的局限在于,
我們飲料可以添加輔料,例如:大杯、加冰、加奶、雙倍咖啡,
我們不可能一種組合就建一個類,那樣類數量就爆炸了,
此時計算價格就非常困難,

將各種調料放在基類中,調料可以是boolean或者資料或者列舉型別,
每種飲料計算價錢的時候就看這些調料是否有,或者有多少,
public class Drink{ private boolean milk; private boolean sugar; public double cost(){ double price = 0d; if(milk){ price +=0.5; } if(sugar){price +=0.1; } return price; } public void addMilk(){ this.mocha=true; } public void addSugar(){ this.whip= true; } } public class DarkCoffee extends Drink{ public double cost(){ double price = super.cost(); price += 2.0; return price; } } 測驗: public class Test{ public static void main(String[] args){ Drink coffee = new DarkCoffee(); coffee.addMocha(); coffee.addWhip(); System.out.println(coffee.cost()); } }通過設定屬性解決調料問題
這種設計存在問題:
1.如果調味品很多,Drink類非常龐大,且新加和洗掉調味品都需要修改類,調料改價格需要調整價格,
2.雙倍調味料的情況boolean值無法滿足,
3.很多調料是互斥的,例如iceCoffee是不能加mocha的,但是他還是繼承了父類的addMocha()方法,這個方法他不適用,他必須覆寫這個方法讓它什么都不做,
不符合【開閉原則】
裝飾模式解釋:
調料可以包裝基礎飲料,因為裝飾者和被裝飾者有相同的超型別,所以可以套娃形式一直套下去,
裝飾者可以在所委托被裝飾者的行為前后,加上自己的行為,已達到特定目的,
計算價格類似于遞回,不斷委托給父類,最后統一回溯,

abstract class Drink{ public String description = "Unknown beverage"; public String getDescription(){ return description; } public abstract double cost(); } // 裝飾類 abstract class CondimentDecorator extends Drink{ public abstract String getDescription(); } // 飲料 class DarkCoffee extends Drink{ public DarkCoffee(){ this.description = "DarkCoffee"; } public double cost(){ return 1.99; } } // 包裝類:調料 class Sugar extends CondimentDecorator{ Drink drink; public Sugar(Drink drink){ this.drink = drink; } public String getDescription(){ return drink.getDescription() + ", add sugar"; } public double cost(){ return .20 + drink.cost(); } } //測驗 class Test{ public static void main(String[] args){ Drink drink = new DarkCoffee(); drink = new Sugar(drink); // 加雙份糖 drink = new Sugar(drink); System.out.println(drink.getDescription() + " ,$"+drink.cost()); //輸出:DarkCoffee, add sugar, add sugar ,$2.39 } }裝飾模式
public interface Drink{ public double cost(); } public class DarkCoffee implements Drink{ public double cost(){ return 2.5; } } public class Decorator implements Drink{ private Drink drink; public Decorator (Drink drink){ this.drink= drink; } public double cost(){ return drink.cost(); } } public class Sugar extends Decorator { public Sugar (Drink drink){ super(drink); } public double cost(){ return super.cost() + 0.1; } }裝飾模式2

裝飾模式:動態將責任附加到物件上,若要拓展功能,裝飾者提供了比繼承更有彈性的替代方案,
裝飾者模式容易造成設計中大量的小類,數量太多,容易把人看懵,例如:java.io

/* 撰寫一個裝飾者,將輸入流內所有大寫字符轉小寫, 我們需要拓展InputStream, */ public class LowerCaseInputStream extends FilterInputStream { public LowerCaseInputStream(InputStream in) { super(in); } public int read() throws IOException { int c = in.read(); return (c == -1 ? c : Character.toLowerCase((char)c)); } public int read(byte[] b, int offset, int len) throws IOException { int result = in.read(b, offset, len); for (int i = offset; i < offset+result; i++) { b[i] = (byte)Character.toLowerCase((char)b[i]); } return result; } } public class InputTest { public static void main(String[] args) throws IOException { int c; InputStream in = null; try { in = new LowerCaseInputStream( new BufferedInputStream( new FileInputStream("test.txt"))); while((c = in.read()) >= 0) { System.out.print((char)c); } } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { in.close(); } } System.out.println(); try (InputStream in2 = new LowerCaseInputStream( new BufferedInputStream( new FileInputStream("test.txt")))) { while((c = in2.read()) >= 0) { System.out.print((char)c); } } catch (IOException e) { e.printStackTrace(); } } }自己包裝IO
例如:我們對HttpServletRequest進行攔截進行處理,實作過濾請求引數,
1). Servlet API 中提供了一個 HttpServletRequestWrapper 類來包裝原始的 request 物件, HttpServletRequestWrapper 類實作了 HttpServletRequest 介面中的所有方法, 這些方法的內部實作都是僅僅呼叫了一下所包裝的的 request 物件的對應方法 //包裝類實作 ServletRequest 介面. public class ServletRequestWrapper implements ServletRequest { //被包裝的那個 ServletRequest 物件 private ServletRequest request; //構造器傳入 ServletRequest 實作類物件 public ServletRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } //具體實作 ServletRequest 的方法: 呼叫被包裝的那個成員變數的方法實作, public Object getAttribute(String name) { return this.request.getAttribute(name); } public Enumeration getAttributeNames() { return this.request.getAttributeNames(); } //... } 2). 作用: 用于對 HttpServletRequest 或 HttpServletResponse 的某一個方法進行修改或增強. public class MyHttpServletRequest extends HttpServletRequestWrapper{ public MyHttpServletRequest(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { String val = super.getParameter(name); if(val != null && val.contains(" fuck ")){ val = val.replace("fuck", "****"); } return val; } } 3). 使用: 在 Filter 中, 利用 MyHttpServletRequest 替換傳入的 HttpServletRequest HttpServletRequest req = new MyHttpServletRequest(request); filterChain.doFilter(req, response);增強HttpServletRequest
例如:在一個方法前后列印時間
// 介面 public interface Dao { public void insert(); public void delete(); public void update(); } // 基礎實作類 public class DaoImpl implements Dao { @Override public void insert() { System.out.println("DaoImpl.insert()"); } @Override public void delete() { System.out.println("DaoImpl.delete()"); } @Override public void update() { System.out.println("DaoImpl.update()"); } } // 包裝類 public class LogDao implements Dao { private Dao dao; public LogDao(Dao dao) { this.dao = dao; } @Override public void insert() { System.out.println("insert()方法開始時間:" + System.currentTimeMillis()); dao.insert(); System.out.println("insert()方法結束時間:" + System.currentTimeMillis()); } @Override public void delete() { dao.delete(); } @Override public void update() { System.out.println("update()方法開始時間:" + System.currentTimeMillis()); dao.update(); System.out.println("update()方法結束時間:" + System.currentTimeMillis()); } } // 呼叫時候 Dao dao = new LogDao(new DaoImpl()); // 對于呼叫方來說,只知道呼叫了dao,不知道加上了日志功能 // 問題:1.輸出日志的邏輯無法復用;2.輸入日志和業務邏輯有耦合,裝飾模式方法前后打日志
和裝飾模式非常相似的還有一個就是代理模式:
代理模式:
為另外一個物件提供一個替身或者占位符控制對這個物件的訪問,

代理模式的典型特點就是,將客戶對subject施加的方法呼叫攔截下來,然后進行自己的處理,
我們通常使用工廠模式,回傳相應物件的代理物件,
其實裝飾模式和代理模式和配接器模式很像,都是包裝一個物件,然后利用這個物件的功能搞出點對外提供的方法,
|
裝飾模式 |
代理模式 |
配接器模式 |
外觀模式 |
|
目的:不改變介面,加入責任 |
目的:控制物件的訪問 |
目的:將一個介面轉成另一個介面 |
目的:讓介面跟簡單 |
|
裝飾者模式可以讓新的行為和責任要加入到設計中,而無需修改現有的代碼, |
代理物件代表物件,不光是為物件加上動作, |
可以整合若干類來提供客戶需要的介面, |
|
|
當一個方法呼叫被委托給裝飾者的時候, 裝飾者有點像遞回套娃,你不知道當前是第幾層, |
可以使用新的庫和子集合,而無需改變任何代碼,配接器會按照原介面給你包好, |
||
|
拓展包裝物件的行為和責任,不是簡單的傳送 |
一定進行介面的裝換, |
示例:多支付系統解決方案

通過加入配接器模式,訂單 Service 在進行支付時呼叫的不再是外部的支付介面,而是“支付方式”介面,與外部系統解耦,
只要保證“支付方式”介面是穩定的,那么訂單 Service 就是穩定的,比如:
當支付寶支付介面發生變更時,影響的只限于支付寶 Adapter;
當微信支付介面發生變更時,影響的只限于微信支付 Adapter;
當要增加一個新的支付方式時,只需要再寫一個新的 Adapter,
日后不論哪種變更,要修改的代碼范圍縮小了,維護成本自然降低了,代碼質量就提高了,
問題:
在劃分微服務程序中,經常糾結,對外的功能有沒有必要抽象出一個代理服務,專門負責與某廠商對接,
根據配接器的理念,如果這個代理服務如果能抽象出標準的介面,那他就有獨立的必要,核心業務服務只和代理服務互動,代理服務屏蔽外部廠商的不同,
日后廠商有變動,我們修改范圍控制在代理服務內,不會涉及到核心業務服務,
例如:我們一般會開發一個統一支付的服務,這個服務對接各個支付系統,對內提供標準支付介面,業務服務只和統一支付互動,
同時統一支付還能幫助我們處理對賬、過期自動退款等功能,讓業務系統穩定,輕便,
本文來自博客園,作者:wanglifeng,轉載請注明原文鏈接: https://www.cnblogs.com/wanglifeng717/p/16348529.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/498981.html
標籤:設計模式
