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

在這裏插入圖片描述

前言

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

分爲三篇文章:java

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

面試知識點複習手冊

經過如下兩種途徑查看全複習手冊文章導航git

快速回憶

結構型程序員

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

理念

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

高內聚低耦合

高內聚:系統中A、B兩個模塊進行交互,若是修改了A模塊,不影響模塊B的工做,那麼認爲A有足夠的內聚。面試

低耦合:就是A模塊與B模塊存在依賴關係,那麼當B發生改變時,A模塊仍然能夠正常工做,那麼就認爲A與B是低耦合的。算法

結構型

適配器(Adapter)

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

定義

客戶類調用適配器的方法時,在適配器類的內部將調用適配者類的方法,而這個過程對客戶類是透明的,客戶類並不直接訪問適配者類。所以,適配器可使因爲接口不兼容而不能交互的類能夠一塊兒工做。這就是適配器模式的模式動機。編程

在這裏插入圖片描述

角色

  • 目標(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)等。

適用場景

一、須要表示一個對象總體或部分層次,在具備總體和部分的層次結構中,但願經過一種方式忽略總體與部分的差別,能夠一致地對待它們。

二、讓客戶可以忽略不一樣對象層次的變化,客戶端能夠針對抽象構件編程,無須關心對象層次結構的細節。

享元模式(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

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

相關文章
相關標籤/搜索