快速梳理23種常用的設計模式(下篇)

qqxx6661發表於2019-02-28

在這裡插入圖片描述

前言

本文旨在快速梳理常用的設計模式,瞭解每個模式主要針對的是哪些情況以及其基礎特徵,每個模式前都有列舉出一個或多個可以深入閱讀的參考網頁,以供讀者詳細瞭解其實現。

分為三篇文章:

  • 上篇:設計模式基礎理念和建立型設計模式
  • 中篇:行為型設計模式
  • 下篇:結構型設計模式

面試知識點複習手冊

通過以下兩種途徑檢視全複習手冊文章導航

快速回憶

結構型

  • 介面卡(Adapter)
  • 裝飾器(Decorator)
  • 代理模式(Proxy)
  • 外觀模式/門面模式(Facade)
  • 橋接模式(Bridge Pattern)
  • 組合模式(Composite)
  • 享元模式(Flyweight)

理念

首先搞清楚一點,設計模式不是高深技術,不是奇淫技巧。設計模式只是一種設計思想,針對不同的業務場景,用不同的方式去設計程式碼結構,其最最本質的目的是為了解耦,延伸一點的話,還有為了可擴充套件性和健壯性,但是這都是建立在解耦的基礎之上。

高內聚低耦合

高內聚:系統中A、B兩個模組進行互動,如果修改了A模組,不影響模組B的工作,那麼認為A有足夠的內聚。

低耦合:就是A模組與B模組存在依賴關係,那麼當B發生改變時,A模組仍然可以正常工作,那麼就認為A與B是低耦合的。

結構型

介面卡(Adapter)

https://www.jianshu.com/p/93821721bf08

定義

客戶類呼叫介面卡的方法時,在介面卡類的內部將呼叫適配者類的方法,而這個過程對客戶類是透明的,客戶類並不直接訪問適配者類。因此,介面卡可以使由於介面不相容而不能互動的類可以一起工作。這就是介面卡模式的模式動機。

在這裡插入圖片描述

角色

  • 目標(Target)角色:這就是所期待得到的介面。注意:由於這裡討論的是類介面卡模式,因此目標不可以是類。

  • 源(Adapee)角色:現在需要適配的介面。

  • 介面卡(Adaper)角色:介面卡類是本模式的核心。介面卡把源介面轉換成目標介面。顯然,這一角色不可以是介面,而必須是具體類。

類介面卡

建立新類,繼承源類,並實現新介面

class  adapter extends oldClass  implements newFunc{}
複製程式碼

物件介面卡

建立新類持源類的例項,並實現新介面

class adapter implements newFunc { private oldClass oldInstance ;}
複製程式碼
  • 類介面卡使用物件繼承的方式,是靜態的定義方式

  • 而物件介面卡使用物件組合的方式,是動態組合的方式。

介面介面卡

建立新的抽象類實現舊介面方法

abstract class adapter implements oldClassFunc { void newFunc();}
複製程式碼

總結

建議儘量使用物件介面卡的實現方式,多用合成/聚合、少用繼承。當然,具體問題具體分析,根據需要來選用實現方式,最適合的才是最好的。

優點

  • 更好的複用性
  • 更好的擴充套件性

缺點

過多的使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是A介面,其實內部被適配成了B介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用介面卡,而是直接對系統進行重構。

裝飾模式(Decorator)

給一類物件增加新的功能,裝飾方法與具體的內部邏輯無關。

實現

設計不同種類的飲料,飲料可以新增配料,比如可以新增牛奶,並且支援動態新增新配料。每增加一種配料,該飲料的價格就會增加,要求計算一種飲料的價格。

下圖表示在 DarkRoast 飲料上新增新新增 Mocha 配料,之後又新增了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它們都繼承自相同父類,都有 cost() 方法,外層類的 cost() 方法呼叫了內層類的 cost() 方法。

在這裡插入圖片描述

程式碼

interface Source{ void method();}
public class Decorator implements Source{

    private Source source ;
    public void decotate1(){
        System.out.println("decorate");
    }
    @Override
    public void method() {
        decotate1();
        source.method();
    }
}
複製程式碼

裝飾模式與代理模式的區別

裝飾器模式關注於在一個物件上動態的新增方法,然而代理模式關注於控制對物件的訪問。

  • 用代理模式,代理類(proxy class)可以對它的客戶隱藏一個物件的具體資訊。因此,當使用代理模式的時候,我們常常在一個代理類中建立一個物件的例項。

  • 當我們使用裝飾器模 式的時候,我們通常的做法是將原始物件作為一個引數傳給裝飾者的構造器。

代理模式(Proxy)

詳細程式碼例項:https://www.cnblogs.com/daniels/p/8242592.html

簡介

代理模式的定義:代理模式給某一個物件提供一個代理物件並由代理物件控制對原物件的引用。

通俗的來講代理模式就是我們生活中常見的中介

為什麼要用代理模式

  • 中介隔離作用

    在某些情況下,一個客戶類不想或者不能直接引用一個委託物件,而代理類物件可以在客戶類和委託物件之間起到中介的作用,其特徵是代理類和委託類實現相同的介面。

  • 開閉原則,增加功能

    真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共的服務。例如我們想給專案加入快取、日誌這些功能,我們就可以使用代理類來完成,而沒必要開啟已經封裝好的委託類。

有哪幾種代理模式

靜態代理

由程式設計師建立或特定工具自動生成原始碼,在對其編譯。在程式設計師執行之前,代理類.class檔案就已經被建立了。

程式碼:靜態代理建立代理類

package main.java.proxy.impl;

import main.java.proxy.BuyHouse;

public class BuyHouseProxy implements BuyHouse {

    private BuyHouse buyHouse;

    public BuyHouseProxy(final BuyHouse buyHouse) {
        this.buyHouse = buyHouse;
    }

    @Override
    public void buyHosue() {
        System.out.println("買房前準備");
        buyHouse.buyHosue();
        System.out.println("買房後裝修");

    }
}
複製程式碼

靜態代理總結

  • 優點:可以做到在符合開閉原則的情況下對目標物件進行功能擴充套件。

  • 缺點:我們得為每一個服務都得建立代理類,工作量太大,不易管理。同時介面一旦發生改變,代理類也得相應修改。

動態代理:JDK反射機制(介面代理)

  • 是在程式執行時通過反射機制動態建立的。

  • 為需要攔截的介面生成代理物件以實現介面方法攔截功能。

程式碼:編寫動態處理器

package main.java.proxy.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxyHandler implements InvocationHandler {

    private Object object;

    public DynamicProxyHandler(final Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("買房前準備");
        Object result = method.invoke(object, args);
        System.out.println("買房後裝修");
        return result;
    }
}
複製程式碼

程式碼:編寫測試類

package main.java.proxy.test;

import main.java.proxy.BuyHouse;
import main.java.proxy.impl.BuyHouseImpl;
import main.java.proxy.impl.DynamicProxyHandler;

import java.lang.reflect.Proxy;


public class DynamicProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
                Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
        proxyBuyHouse.buyHosue();
    }
}
複製程式碼

動態代理總結

  • 優勢:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務介面的依賴,降低了耦合度。

  • 劣勢:只能對介面進行代理

動態代理:CGLIB代理

  • 其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯

  • 但因為採用的是繼承,所以不能對final修飾的類進行代理

  • JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。

程式碼見網頁

CGLIB代理總結:(與JDK代理區別:)

CGLIB建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLIB建立代理物件時所花費的時間卻比JDK多得多

  • 所以對於單例的物件,因為無需頻繁建立物件,用CGLIB合適,反之使用JDK方式要更為合適一些。
  • 同時由於CGLib由於是採用動態建立子類的方法,對於final修飾的方法無法進行代理。

外觀模式/門面模式(Facade)

https://www.cnblogs.com/lthIU/p/5860607.html

在這裡插入圖片描述

在這裡插入圖片描述

簡單來說,該模式就是把一些複雜的流程封裝成一個介面供給外部使用者更簡單的使用。這個模式中,涉及到3個角色。

角色

1)門面角色:外觀模式的核心。它被客戶角色呼叫,它熟悉子系統的功能。內部根據客戶角色的需求預定了幾種功能的組合。

2)子系統角色: 實現了子系統的功能。它對客戶角色和Facade時未知的。它內部可以有系統內的相互互動,也可以由供外界呼叫的介面。

3)客戶角色: 通過呼叫Facede來完成要實現的功能。

程式碼

package com.huawei.facadeDesign.facade;

import org.apache.log4j.Logger;

import com.huawei.facadeDesign.children.CPU;
import com.huawei.facadeDesign.children.Disk;
import com.huawei.facadeDesign.children.Memory;


/**
 * 門面類(核心)
 * @author Administrator
 *
 */
public class Computer
{
    public static final Logger LOGGER = Logger.getLogger(Computer.class);
    
    private CPU cpu;
    private Memory memory;
    private Disk disk;
    public Computer()
    {
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }
    public void start()
    {
        LOGGER.info("Computer start begin");
        cpu.start();
        disk.start();
        memory.start();
        LOGGER.info("Computer start end");
    }
    
    public void shutDown()
    {
        LOGGER.info("Computer shutDown begin");
        cpu.shutDown();
        disk.shutDown();
        memory.shutDown();
        LOGGER.info("Computer shutDown end...");
    }
}
複製程式碼

優點

  • 鬆散耦合:使得客戶端和子系統之間解耦,讓子系統內部的模組功能更容易擴充套件和維護;

  • 簡單易用:客戶端根本不需要知道子系統內部的實現,或者根本不需要知道子系統內部的構成,它只需要跟Facade類互動即可。

  • 更好的劃分訪問層次:有些方法是對系統外的,有些方法是系統內部相互互動的使用的。子系統把那些暴露給外部的功能集中到門面中,這樣就可以實現客戶端的使用,很好的隱藏了子系統內部的細節。

橋接模式(Bridge Pattern)

http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html

含義

在軟體系統中,某些型別由於自身的邏輯,它具有兩個或多個維度的變化,那麼如何應對這種“多維度的變化”?

如何利用面嚮物件的技術來使得該型別能夠輕鬆的沿著多個方向進行變化,而又不引入額外的複雜度?這就要使用Bridge模式。

在這裡插入圖片描述

由上圖變為下圖

在這裡插入圖片描述

程式碼

詳細程式碼見參考網頁

static void Main(string[] args){
    //男人開著公共汽車在高速公路上行駛;
    Console.WriteLine("=========================");
    AbstractRoad Road3 = new SpeedWay();
    Road3.Car = new Bus();
    people p = new Man();
    p.Road = Road3;
    p.Run();
    Console.Read();
}
複製程式碼

組合模式(Composite)

https://www.cnblogs.com/lfxiao/p/6816026.html

組合模式是為了表示那些層次結構,同時部分和整體也可能是一樣的結構,常見的如資料夾或者樹.

定義

組合模式定義瞭如何將容器物件和葉子物件進行遞迴組合,使得客戶在使用的過程中無須進行區分,可以對他們進行一致的處理。

在使用組合模式中需要注意一點也是組合模式最關鍵的地方:葉子物件和組合物件實現相同的介面。這就是組合模式能夠將葉子節點和物件節點進行一致處理的原因。

角色

1.Component :組合中的物件宣告介面,在適當的情況下,實現所有類共有介面的預設行為。宣告一個介面用於訪問和管理Component子部件。

2.Leaf:葉子物件。葉子結點沒有子結點。

3.Composite:容器物件,定義有枝節點行為,用來儲存子部件,在Component介面中實現與子部件有關操作,如增加(add)和刪除(remove)等。

適用場景

1、需要表示一個物件整體或部分層次,在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,可以一致地對待它們。

2、讓客戶能夠忽略不同物件層次的變化,客戶端可以針對抽象構件程式設計,無須關心物件層次結構的細節。

享元模式(Flyweight)

https://www.cnblogs.com/chenssy/p/3330555.html

定義

所謂享元模式就是執行共享技術有效地支援大量細粒度物件的複用,所以享元模式要求能夠共享的物件必須是細粒度物件。

  • 內部狀態:在享元物件內部不隨外界環境改變而改變的共享部分。

  • 外部狀態:隨著環境的改變而改變,不能夠共享的狀態就是外部狀態。

程式碼

享元工廠類FlyweightFactory:

利用了HashMap儲存已經建立的顏色

public class FlyweightFactory{
    static Map<String, Shape> shapes = new HashMap<String, Shape>();
    
    public static Shape getShape(String key){
        Shape shape = shapes.get(key);
        //如果shape==null,表示不存在,則新建,並且保持到共享池中
        if(shape == null){
            shape = new Circle(key);
            shapes.put(key, shape);
        }
        return shape;
    }
    
    public static int getSum(){
        return shapes.size();
    }
}
複製程式碼

客戶端程式:Client.java

public class Client {
    public static void main(String[] args) {
        Shape shape1 = FlyweightFactory.getShape("紅色");
        shape1.draw();
        
        Shape shape2 = FlyweightFactory.getShape("灰色");
        shape2.draw();
        
        Shape shape3 = FlyweightFactory.getShape("綠色");
        shape3.draw();
        
        Shape shape4 = FlyweightFactory.getShape("紅色");
        shape4.draw();
        
        Shape shape5 = FlyweightFactory.getShape("灰色");
        shape5.draw();
        
        Shape shape6 = FlyweightFactory.getShape("灰色");
        shape6.draw();
        
        System.out.println("一共繪製了"+FlyweightFactory.getSum()+"中顏色的圓形");
    }
}
複製程式碼

參考

簡書大牛:https://www.jianshu.com/nb/10186551

Github:https://github.com/CyC2018/Interview-Notebook/blob/master/notes/設計模式.md

菜鳥網:http://www.runoob.com/design-pattern/design-pattern-tutorial.html

補充:

23種設計模式總結:

https://www.cnblogs.com/tongkey/p/7170826.html

https://www.cnblogs.com/malihe/p/6891920.html

-----正文結束-----

更多精彩文章,請查閱我的部落格或關注我的公眾號:Rude3Knife

全複習手冊文章導航

知識點複習手冊文章推薦

關注我

我是蠻三刀把刀,目前為後臺開發工程師。主要關注後臺開發,網路安全,Python爬蟲等技術。

來微信和我聊聊:yangzd1102

Github:https://github.com/qqxx6661

原創部落格主要內容

  • 筆試面試複習知識點手冊
  • Leetcode演算法題解析(前150題)
  • 劍指offer演算法題解析
  • Python爬蟲相關技術分析和實戰
  • 後臺開發相關技術分析和實戰

同步更新以下部落格

1. Csdn

blog.csdn.net/qqxx6661

擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發、面試助攻手冊

2. 知乎

www.zhihu.com/people/yang…

擁有專欄:碼農面試助攻手冊

3. 掘金

juejin.im/user/5b4801…

4. 簡書

www.jianshu.com/u/b5f225ca2…

個人公眾號:Rude3Knife

個人公眾號:Rude3Knife

如果文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

相關文章