代碼寫的爛,經常被同事懟,教你一招!
本文轉載自微信公眾號「微觀技術」,作者Tom哥。轉載本文請聯系微觀技術公眾號。
大家好,我是Tom哥~
面對復雜的業務場景,千變萬化的客戶需求,如何以一變應萬變,以最小的開發成本快速落地實現,同時保證系統有著較低的復雜度,能夠保證系統后續de持續迭代能力,讓系統擁有較高的可擴展性。
這些是一個合格的架構師必須修煉的基礎內功,但是如何修煉這門神功???
我將常用的軟件設計模式,做了匯總,目錄如下:
(考慮到內容篇幅較大,為了便于大家閱讀,將軟件設計模式系列(共23個)拆分成四篇文章,每篇文章講解六個設計模式,采用不同的顏色區分,便于快速消化記憶)
本文是主要講解橋接模式、組合模式、裝飾模式、門面模式、代理模式、責任鏈模式
1、橋接模式
自然界一般由實體和行為組成。當然為了提升系統的擴展性,它們兩個又可以各自抽象,然后在抽象類中描述兩者的依賴。
定義:
將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
什么場景使用橋接模式?
- 一個類存在兩個(或多個)獨立變化的維度,且這兩個(或多個)維度都需要獨立進行擴展。
- 對于那些不希望使用繼承或因為多層繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。
核心思路:
- 抽象實體:定義的一種抽象分類。比如:人
- 具體實體:繼承抽象實體的子類實體。比如:中國人、美國人、韓國人
- 抽象行為:定義抽象實體中具備的多種行為。比如:學漢語、吃漢堡
- 具體行為:實現抽象行為的具體算法。比如:中國人學漢語、美國人吃漢堡
代碼示例:
- /**
- * @author 微信公眾號:微觀技術
- * 抽象實體
- */
- public abstract class AbstractEntity {
- protected AbstractBehavior abstractBehavior;
- public AbstractEntity(AbstractBehavior abstractBehavior) {
- this.abstractBehavior = abstractBehavior;
- }
- public abstract void out();
- }
- /**
- * 抽象行為
- */
- public interface AbstractBehavior {
- public String action(String name);
- }
- /**
- * 關于食物的行為
- */
- public class FoodBehavior implements AbstractBehavior {
- @Override
- public String action(String name) {
- if ("中國人".equals(name)) {
- return "吃 餃子";
- } else if ("美國人".equals(name)) {
- return "吃 漢堡";
- }
- return null;
- }
- }
橋接模式是將抽象與抽象之間分離,具體實現類依賴于抽象。抽象的分離間接完成了具體類與具體類之間的解耦,它們之間使用抽象來進行組合或聚合,而不再靠多重繼承來實現。本質是將一個對象的實體和行為分離,然后再基于這兩個維度進行獨立的演化。
適用場景:
- 拆分復雜的類對象時。當一個類中包含大量對象和方法時,既不方便閱讀,也不方便修改。
- 希望從多個獨立維度上擴展時。比如,系統功能性和非功能性角度,業務或技術角度等。
- 運行時,組合不同的組件
2、組合模式
定義:
組合模式也稱整體模式,把一組相似的對象當作一個單一的對象,然后將對象組合成樹形結構以表示整個層次結構。
這里邊有兩個關鍵點:1、樹形結構分層 2、業務統一化來簡化操作
核心思路:
- 抽象組件(AbstractNode):定義需要實現的統一操作。
- 組合節點(CompositeNode):抽象組件的衍生子類,包含了若干孩子節點(其它組合節點或葉子節點)。
- 葉子節點(LeafNode):抽象組件的子類,但它的下面沒有子節點。
代碼示例:
- public abstract class AbstractNode {
- public abstract void add(AbstractNode abstractNode);
- public abstract void remove(AbstractNode abstractNode);
- public abstract void action();
- }
- public class CompositeNode extends AbstractNode {
- private Long nodeId;
- private List<AbstractNode> childNodes; //存放子節點列表
- public CompositeNode(Long nodeId, List<AbstractNode> childNodes) {
- this.nodeId = nodeId;
- this.childNodes = childNodes;
- }
- @Override
- public void add(AbstractNode abstractNode) {
- childNodes.add(abstractNode);
- }
- @Override
- public void remove(AbstractNode abstractNode) {
- childNodes.remove(abstractNode);
- }
- @Override
- public void action() {
- for (AbstractNode childNode : childNodes) {
- childNode.action();
- }
- }
- }
- public class LeafNode extends AbstractNode {
- private Long nodeId;
- public LeafNode(Long nodeId) {
- this.nodeId = nodeId;
- }
- @Override
- public void add(AbstractNode abstractNode) {
- // 無子節點,無需處理
- return;
- }
- @Override
- public void remove(AbstractNode abstractNode) {
- // 無子節點,無需處理
- return;
- }
- @Override
- public void action() {
- System.out.println("葉子節點編號:" + nodeId);
- }
- }
葉子節點不能新增、刪除子節點,所以對應的方法為空。
組合模式本質上封裝了復雜結構的內在變化,讓使用者通過一個統一的整體來使用對象之間的結構。數據結構方面支持樹形結構、環形結構、網狀結構。如我們常見的 深度優先搜索、廣度優先搜索都是采用這種模式。
適用場景:
- 一組對象按照某種層級結構進行管理。如:管理文件夾和文件,管理訂單下的商品。
- 需要按照統一的行為來處理復雜結構中的對象
- 快速擴展對象組合。
手機開始是按品牌來歸屬分類,現在業務增加價格維度分類,我們只需要引入新的分支節點,按新的維度構建組合關系。
3、裝飾模式
定義:
動態地向一個現有對象添加新的職責和行為,同時又不改變其結構,相當于對現有的對象進行包裝。
核心思路:
- 抽象組件(Component):裝飾器基類,定義組件的基本功能
- 具體組件(ConcreteComponent):抽象組件的具體實現
- 抽象裝飾器(Decorator):包含抽象組件的引用
- 具體裝飾器(ConcreteDecorator):抽象裝飾器的子類,并重寫組件接口方法,同時可以添加附加功能。
代碼示例:
- public abstract class Component {
- public abstract void execute();
- }
- public class ConcreteComponent extends Component {
- @Override
- public void execute() {
- System.out.println("具體子類 ConcreteComponent invoke !");
- }
- }
- public class Decorator extends Component {
- protected Component component;
- public Decorator(Component component) {
- this.component = component;
- }
- @Override
- public void execute() {
- component.execute();
- }
- }
- public class ConcreteDecorator extends Decorator {
- public ConcreteDecorator(Component component) {
- super(component);
- }
- @Override
- public void execute() {
- System.out.println("裝飾器子類 ConcreteDecorator invoke !");
- super.execute();
- }
- }
裝飾模式本質上就是給已有不可修改的類附加新的功能,同時還能很方便地撤銷。
適用場景:
- 無需修改代碼的情況下即可使用對象, 且希望在運行時為對象新增額外的功能
- 將業務邏輯組織為層次結構,可以為各層創建一個裝飾,在運行時將各種不同邏輯組合成對象。由于這些對象都遵循通用接口,客戶端代碼能以相同的方式使用這些對象。
- 不支持繼承擴展類的場景。如:final 關鍵字限制了某個類的進一步擴展,可以通過裝飾器對其進行封裝,從而具備擴展能力。
4、門面模式
定義:
- 門面模式提供一個高層次的接口,要求一個子系統的外部與其內部的通信必須通過一個統一的對象進行,使得子系統更易于使用。
- 門面模式要求我們使用統一的標準與系統交互,比如:我們打印日志基本會選擇slf4j框架,其內部統一了log4j、log4j2、CommonLog等日志框架,簡化了我們的開發成本。
核心思路:
門面系統。接收外部請求,并將請求轉發給適當的子系統進行處理
子系統。表示某個領域內的功能實現、或者具體子接口實現,比如,訂單、支付等,專門處理由門面系統指派的任務。
簡單來講,引入一個外觀角色來簡化客戶端與子系統之間的交互,為復雜的子系統調用提供一個統一的入口。
可能很多人有疑問,這個不就是代理模式嗎?
門面模式可能代理的是多個接口,而代理模式通常只是代理一個接口。
業務場景:
移動互聯網,我們都習慣了在線支付,相信很多人在付款時都聽過這么一句話,”微信支付還是支付寶“,商戶根據用戶反饋再針對性選擇收款渠道。
是不是很繁瑣,為了解決這個問題,市面就有了聚合支付(該領域做非常棒的是收錢吧),整個業務模式就是這節要講的門面模式,不管你用什么軟件支付,只要打開付款二維碼即可,收錢吧底層識別解析二維碼,并根據掃描結果自動適配對應的收款渠道,完成用戶的扣款動作,確實帶來不錯的用戶體驗。
優點:
- 簡化復雜系統,提供統一接口規范。比如:JPA提供了統一Java持久層API,底層適配多樣化的存儲系統。
- 復雜的業務邏輯由內部子系統消化,只要對外接口規范不變,外部調用方不需要頻繁修改
- 擴展性較好,類似于SPI架構一樣,支持水平擴展。
- 較高的平滑過渡性。比如:我們要對老的系統架構升級,開發一系列新接口來替換原來的老接口,過渡期需要新老灰度測試、流量切換、平滑升級,可以采用該模式。門面模式在兼容多套系統、系統重構方面是把利器。
5、代理模式
定義:
為其他對象提供一種代理以控制對這個對象的訪問
現實場景:
- 房產中介
- 包工頭
核心思路:
- 抽象主題類(AbstractSubject):定義接口方法,供客戶端使用
- 主題實現類(RealSubject):實現了抽象主題類的接口方法
- 代理類(Proxy):實現了抽象主題類的接口方法,內部包含主題實現類的邏輯, 同時還包含一些自身的擴展操作。
代理模式與適配器模式相似。但適配器模式是轉換為新的接口,而代理模式不會改變原有接口。
代碼示例:
- /**
- * @author 微信公眾號:微觀技術
- */
- public interface AbstractSubject {
- void execute();
- }
- public class RealSubject implements AbstractSubject {
- @Override
- public void execute() {
- System.out.println("我是Tom哥,我要努力工作!");
- }
- }
- public class Proxy implements AbstractSubject {
- private AbstractSubject abstractSubject;
- public Proxy(AbstractSubject abstractSubject) {
- this.abstractSubject = abstractSubject;
- }
- @Override
- public void execute() {
- System.out.println("老板給Tom哥分配工作了。。。");
- abstractSubject.execute();
- }
- }
按使用職責分為靜態代理和動態代理。
- 靜態代理,代理類需要自己編寫代碼完成。
- 動態代理,代理類通過 Proxy#newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) 方法生成。
- JDK實現的代理中不管是靜態代理還是動態代理,都是面向接口編程。CGLib可以不限制一定是接口。
優點:
- 職責清晰
- 高擴展,只要實現了接口,都可以用代理
- 智能化,動態代理
- 降低了對象的直接耦合
適用場景:
- 遠程代理。無法直接操作遠程對象。比如:Dubbo、gRPC,提供遠程服務,客戶端調用時需要走參數組裝、序列化、網絡傳輸等操作,這些通用邏輯都可以封裝到代理中,客戶端調用代理對象訪問遠程服務,就像調用本地對象一樣方便。
- 保護代理。當客戶端通過代理對象訪問原始對象時,代理對象會根據規則判斷客戶端是否有權限訪問。比如:防火墻
- 日志代理。比如:日志監控,正常業務訪問時,調用代理,增加一些額外的日志記錄功能。
- 虛擬代理,適用于延遲初始化,用小對象表示大對象的場景,減少資源損耗,提升運行速度。
- 不希望改變原對象,但需要增加類似于權限控制、日志、流控等附加功能時,可以使用代理模式。
6、責任鏈模式
定義:
責任鏈模式是一種行為設計模式,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈。收到請求后,每個處理者均可對請求進行處理,或將其傳遞給鏈中的下個處理者。
責任鏈模式是對數據結構中的鏈表結構的具體應用。
核心思路:
- 抽象處理者(Handler):定義一個接口,內部包含處理方法和下一個節點的引用對象
- 具體處理者(ConcreteHandler):抽象處理者的實現子類,判斷本次請求是否處理,如果需要則處理,否則跳過,然后將請求轉發給下一個節點。
優點:
- 降低了對象之間的耦合度。鏈上各個節點各司其職,通過上下文傳遞數據,避免直接依賴。
- 增強系統的可擴展性。如果有新的業務需求,只需要在合適的位置增加一個鏈節點即可,滿足開閉原則。
- 靈活性強。如果業務有變化,需要對工作流程做調整,只需要動態調整鏈上節點的次序即可。甚至為了滿足多元化業務的多樣化需求,我們可以為不同的業務類型定義自己的專屬執行順序。
- 簡化了對象之間的連接。每個對象只需保存下一個節點的引用,而不需保持所有節點。
- 責任明確。每個節點只需處理自己的工作,如果不處理則傳遞給下一個對象。明確各類的責任范圍,符合類的單一職責原則。
像我們常見的網關架構推薦使用該模式,通過服務編排,可以自由地在任意位置添加或移除節點,滿足一系列個性化功能。
寫在最后
設計模式很多人都學習過,但項目實戰時總是暈暈乎乎,原因在于沒有了解其核心是什么,底層邏輯是什么,《設計模式:可復用面向對象的基礎》有講過,
在設計中思考什么應該變化,并封裝會發生變化的概念。
軟件架構的精髓:找到變化,封裝變化。
業務千變萬化,沒有固定的編碼答案,千萬不要硬套設計模式。無論選擇哪一種設計模式,盡量要能滿足SOLID原則,自我review是否滿足業務的持續擴展性。有句話說的好,“不論白貓黑貓,能抓老鼠就是好貓。”