日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

設計模式類別,設計模式遵循的7個原則,工廠模式(靜態工廠,工廠方法,抽象工廠),7種單例模式

作者:打乒乓球只會抽 更新時間: 2022-09-26 編程語言

設計模式01-單例和工廠

設計模式概念

模式是一套被反復使用、多數人知曉的、經過分類編寫的、成功代碼設計經驗的總結;它不是語法規定,而是一套用來提高代碼可復用性、可維護性、可讀性、穩健性以及安全性的解決方案。 (性能,安全,可靠)

設計模式的作用

  1. 可以提高程序員的思維能力、編程能力和設計能力。
  2. 使程序設計更加標準化、代碼編制更加工程化,使軟件開發效率大大提高,從而縮短軟件的開發周期。
  3. 使設計的代碼可重用性高、可讀性強、可靠性高、靈活性好、可維護性強。

設計模式類別

分為三大類:

  • 創建型模式(5種):工廠方法模式,抽象工廠模式,單例模式,建造者模式,原型模式。
  • 結構型模式(7種):適配器模式,裝飾器模式,代理模式,外觀模式,橋接模式,組合模式,享元模式。
  • 行為型模式(11種):策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。

設計模式遵循的原則有7個:

1. 開閉原則(Open Close Principle)

對擴展開放,對修改關閉。

問題:在軟件的生命周期內,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進行重構,并且需要原有代碼經過重新測試。
解決方案:當軟件需要變化時,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化。

2. 里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。
里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。
LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規范。
問題 :有一功能P1,由類A完成。現需要將功能P1進行擴展,擴展后的功能為P,其中P由原有功能P1與新功能P2組成。新功能P由類A的子類B來完成,則子類B在完成新功能P2的同時,有可能會導致原有功能P1發生故障。

解決方案:當使用繼承時,遵循里氏替換原則。類B繼承類A時,除添加新的方法完成新增功能P2外,盡量不要重寫父類A的方法,也盡量不要重載父類A的方法。【有時候我們可以采用final的手段強制來遵循】

3. 依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,對接口編程,依賴于抽象而不依賴于具體。高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象。
問題:類A直接依賴類B,假如要將類A改為依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責復雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。
解決方案將類A修改為依賴接口I,類B和類C各自實現接口I,類A通過接口I間接與類B或者類C發生聯系,則會大大降低修改類A的幾率。

4. 接口隔離原則(Interface Segregation Principle)

使用多個隔離的接口來降低耦合度。
問題:類A通過接口I依賴類B,類C通過接口I依賴類D,如果接口I對于類A和類B來說不是最小接口,則類B和類D必須去實現他們不需要的方法。
解決方案將臃腫的接口I拆分為獨立的幾個接口,類A和類C分別與他們需要的接口建立依賴關系。也就是采用接口隔離原則。

5. 迪米特法則(最少知道原則)(Demeter Principle)

一個實體應當盡量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。
問題:類與類之間的關系越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。
解決方法盡量降低類與類之間的耦合。

6. 合成復用原則(Composite Reuse Principle)

原則是盡量使用合成/聚合的方式,而不是使用繼承。繼承實際上破壞了類的封裝性,超類的方法可能會被子類修改。
問題:B類如果繼承了A類,A類可繼承方法m的實現細節暴露給B類,如果A類發生方法m改變,那么B的實現也不得不發生改變。
解決方法使用合成或者聚合,不要使用繼承

7. 單一職責原則(Single responsibility principle)

一個類只負責一個功能領域的響應職責。
如果一個類承擔的職責過多,就等于把這些職責耦合在一起,一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力。另外,多個職責耦合在一起,會影響復用性。
問題:比如一個類T負責兩個不同的職責:職責P1,職責P2。當由于職責P1需求發生改變而需要修改類T時,有可能會導致原本運行正常的職責P2功能發生故障。
解決方法遵循單一職責原則。分別建立兩個類T1、T2,使T1完成職責P1功能,T2完成職責P2功能。這樣,當修改類T1時,不會使職責P2發生故障風險;同理,當修改T2時,也不會使職責P1發生故障風險。

常用設計模式

工廠模式

工廠模式屬于創建型設計模式,它提供了一種創建對象的最佳方式。隱藏復雜的邏輯處理過程, 只關心執行結果。直接用new可以完成的不需要用工廠模式。在需要生成復雜對象的地方使用。

靜態工廠模式

靜態工廠模式又可以稱為簡單工廠模式
概念:在簡單工廠模式中,可以根據參數的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的父類。(生成同類型產品)
優點:功能強大,對象創建和使用分離,程序員可以只關心對象使用,不用關心對象如何創建
缺點:耦合度高(所有產品都在工廠創建,一旦異常其他產品也受影響),擴展性不強(每次添加一個產品,工廠類都要變化),違背開閉原則 。

  1. 定義接口
package com.aaa.fatory;

/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 14:52
 * @description:學習軟件開發技術
 * @modified By:
 * @version: 1.0
 */
public interface SoftwareTechnology {
    void  studyST();
}
  1. 編寫實現類

  2. 測試

	@Test
    public void  staticFactoryTest(){
        SoftwareTechnology java = TechnologyFactory.teach(1);
        java.studyST();
        SoftwareTechnology python = TechnologyFactory.teach(2);
        python.studyST();
    }

兩個對象都被創建出來了
在這里插入圖片描述

工廠(方法)模式:

工廠方法模式定義一個用于創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。
概念解釋:
工廠本身不再創建產品,而是規定了工廠規范,即工廠接口,而將產品創建都交給子工廠創建。
優點: 遵循了開閉原則(不需要修改工廠類,就可以增加產品 解耦,職責單一(每個工廠只負責創建對應的產品)
缺點: 增加系統復雜度(每新加一個產品需要新加一個工廠)

  1. 定義工廠接口
package com.aaa.designmode.factory;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 15:04
 * @description:總工廠,相當于總公司,只定義規范 ,不參與生產
 * @modified By:
 * @version:
 */
public interface TechnologyFactoryInterface {
    /**
     * 生產規范,教學規范
     */
    SoftwareTechnology teachST();
}
  1. 編寫工廠實現類
package com.aaa.designmode.factory;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 15:06
 * @description:
 * @modified By:
 * @version:
 */
public class ZhengZhouFactory implements TechnologyFactoryInterface{
    @Override
    public SoftwareTechnology teachST() {
        return new JavaTechnology();
    }
}
package com.aaa.designmode.factory;

/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 15:06
 * @description:
 * @modified By:
 * @version:
 */
public class WuHanFactory implements TechnologyFactoryInterface{
    @Override
    public SoftwareTechnology teachST() {
        return new PythonTechnology();
    }
}

測試

	@Test
    public void factoryMethodTest(){
        //學Java
        TechnologyFactoryInterface zhengZhouFactory = new ZhengZhouFactory();
        SoftwareTechnology softwareTechnology1 = zhengZhouFactory.teachST();
        softwareTechnology1.studyST();
        //學python
        TechnologyFactoryInterface wuHanFactory = new WuHanFactory();
        SoftwareTechnology softwareTechnology2 = wuHanFactory.teachST();
        softwareTechnology2.studyST();
    }

同樣兩個對象也被創建出來了
在這里插入圖片描述

抽象工廠

概念:抽象工廠是工廠方法的升級版,為相關或者相互依賴的對象提供一個接口,而且無須指定他們的具體實現類。
概念解釋:抽象工廠模式相對于工廠方法模式來說,就是工廠方法模式是針對一個產品系列的,而抽象工廠模式是針對多個產品系列的,即工廠方法模式是一個產品系列一個工廠類,而抽象工廠模式是多個產品系列一個工廠類
優點:當一個產品族中的多個對象被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的對象。
缺點:難以支持新種類的產品。因為抽象工廠接口確定了可以被創建的產品集合,所以難以擴展抽象工廠以生產新種類的產品。

  1. 定義抽象接口(包括簡單和工廠方法)
package com.aaa.fatory;

/**
 * @author : Student尚
 * @version : 1.0
 * @createTime : 2022/9/25 18:31
 * @description :
 */
public interface HardwareTechnology {
    void  studyHT();
}
package com.aaa.designmode.factory;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 15:04
 * @description:
 * @modified By:
 * @version:
 */
public interface TechnologyFactoryAbstractInterface {
    /**
     * 生產規范,返回軟件技術
     */
    SoftwareTechnology teachST();
    /**
     * 生產規范,返回硬件技術
     */
    HardwareTechnology teachHT();
}
  1. 編寫實現工廠
package com.aaa.designmode.factory;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 15:06
 * @description:
 * @modified By:
 * @version:
 */
public  class ZhengZhouFactoryNew implements TechnologyFactoryAbstractInterface{
    @Override
    public SoftwareTechnology teachST() {
        return new JavaTechnology();
    }
    @Override
    public HardwareTechnology teachHT() {
        return new PhoneTechnology();
    }
}

測試

 @Test
    public void factoryAbstract(){
        TechnologyFactoryAbstractInterface tfai = new ZhengZhouFactoryNew();
        SoftwareTechnology softwareTechnology = tfai.teachST();
        softwareTechnology.studyST();
        HardwareTechnology hardwareTechnology = tfai.teachHT();
        hardwareTechnology.studyHT();
    }

總結:
無論是簡單工廠模式,工廠方法模式,還是抽象工廠模式,他們都屬于工廠模式,在形式和特點上也是極為相似的,他們的最終目的都是為了解耦,讓類的創建和使用過程實現松耦合。

單例(態)模式

概念:
一種常用的軟件設計模式。所謂單例,就是讓一個類在項目運行中只存在一個對象,即使用到這個類的地方很多,也只存在一個對象。
好處:

  1. 節省內存
  2. 有些情況下不用單例模式可能會引起代碼邏輯錯誤(例如:網站訪問量統計功能 application.setAttrbute(“count”,100);

單例模式要點:

  1. 單例模式的類只提供私有的構造函數
  2. 類定義中含有一個該類的靜態私有對象;
  3. 該類提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。
    實現方式(7種):

懶漢(slacker):該單例類非常懶,只有在自身需要的時候才會行動,從來不知道及早做好準備。特點是運行時獲得對象的速度比較慢,但加載類的時候比較快。整個應用的生命周期只有一部分時間在占用資源。

1. 懶漢線程不安全:

package com.aaa.designmode.singleton;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 16:09
 * @description:
 * @modified By:
 * @version:
 */
public class SlackerSingleton {
    /**
     * 1、構造器私有
     */
    private SlackerSingleton() {
    }
    /**
     * 2. 類定義中含有一個該類的靜態私有對象;
     */
    private static SlackerSingleton singleton;
    /**
     * 3. 該類提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。
     */
    public static SlackerSingleton getInstance(){
        if(singleton ==null){
            singleton = new SlackerSingleton();
        }
        return singleton;
    }
}

測試:

@Test
    public void singletonTest(){
        Person person1 = new Person();
        Person person2 = new Person();
        //普通類new出來的內存區域不同,所以下面的返回值為false
        System.out.println(person1==person2);
        SlackerSingleton singleton1 = SlackerSingleton.getInstance();
        SlackerSingleton singleton2 = SlackerSingleton.getInstance();
        System.out.println(singleton1==singleton2);
    }

在這里插入圖片描述

2. 懶漢線程安全

上面的getinstance()方法在單線程的場景下不會出現問題,但是在多線程場景下,會出現線程安全問題。

package com.aaa.designmode.singleton;

/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 16:09
 * @description:
 * @modified By:
 * @version:
 */
public class SlackerSingleton {
    /**
     * 1、構造器私有
     */
    private SlackerSingleton() {
    }

    /**
     * 2. 類定義中含有一個該類的靜態私有對象;
     */
    private static SlackerSingleton singleton;

    /**
     * 3. 該類提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。
     */
    public static SlackerSingleton getInstance(){
        if(singleton ==null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new SlackerSingleton();
        }
        return singleton;
    }
}
package com.aaa.designmode.singleton;

/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 16:06
 * @description:
 * @modified By:
 * @version:
 */
public class TestSingleton {
    public static void main(String[] args) {
       Runnable r1= () -> {
           SlackerSingleton instance = SlackerSingleton.getInstance();
           System.out.println(instance);
       };
       Runnable r2= () -> {
           SlackerSingleton instance = SlackerSingleton.getInstance();
           System.out.println(instance);
       };
        for (int i = 0; i < 20; i++) {
            new Thread(r1).start();
            new Thread(r2).start();
        }
    }
}

多線程測試不要在Test方法里測,要在main方法測試,有可能測試失敗(已踩坑)
在這里插入圖片描述

可以發現內存地址不一致,說明不是單例的。
解決方案:加同步鎖

package com.aaa.singleton;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 16:09
 * @description:
 * @modified By:
 * @version:
 */
public class SlackerSingleton {
    /**
     * 1、構造器私有
     */
    private SlackerSingleton() {
    }
    /**
     * 2. 類定義中含有一個該類的靜態私有對象;
     */
    private static SlackerSingleton singleton;
    /**
     * 3. 該類提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。
     */
    public static synchronized SlackerSingleton getInstance(){
        if(singleton == null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            singleton = new SlackerSingleton();
        }
        return singleton;
    }
}

同樣的測試代碼:
在這里插入圖片描述

3. 懶漢線程安全雙重加鎖(難點)

此代碼看上去沒什么問題,但是在同一時間多線程的情況下,可能出現JVM指令重排的問題,從而導致某一個線程獲取的單例對象沒有初始化對象。
指令重排
在這里插入圖片描述
指令重排為了提高性能,在遵守 as-if-serial 語義(即不管怎么重排序,單線程下程序的執行結果不能被改變。編譯器,runtime 和處理器都必須遵守。)的情況下,編譯器和處理器常常會對指令做重排序。

一般重排序可以分為如下三種類型:

編譯器優化重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
指令級并行重排序:現代處理器采用了指令級并行技術來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
內存系統重排序:由于處理器使用緩存和讀 / 寫緩沖區,這使得加載和存儲操作看上去可能是在亂序執行。

int a = 0;
 
// 線程 A
a = 1;           			// 1
boolen  flag = true;     	// 2
 
// 線程 B
if (flag) { 		// 3
  int i = a; 		// 4
}

單看上面的程序好像沒有問題,最后 i 的值是 1。但是為了提高性能,編譯器和處理器常常會在不改變數據依賴的情況下對指令做重排序。
?
假設線程 A 在執行時被重排序成先執行代碼 2,再執行代碼 1; 而線程 B 在線程 A 執行完代碼 2 后,讀取了 flag變量。由于條件判斷為真,線程 B 將讀取變量 a。此時,變量 a 還根本沒有被線程 A 寫入,那么 i 最后的值是 0,導致執行結果不正確。
?
那么如何程序執行結果正確呢?這里可以使用 volatile 關鍵字
?
這個例子中, 使用 volatile 不僅保證了變量的內存可見性,還禁止了指令的重排序,即保證了 volatile 修飾的變量編譯后的順序與程序的執行順序一樣。那么使用 volatile 修飾 flag 變量后,在線程 A 中,保證了代碼 1 的執行順序一定在代碼 2 之前。

volatile 禁止編譯器進行指令重排

package com.aaa.designmode.singleton;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 16:09
 * @description:
 * @modified By:
 * @version:
 */
public class SlackerSingleton {
    /**
     * 1、構造器私有
     */
    private SlackerSingleton() {
    }
    /**
     * 2. 類定義中含有一個該類的靜態私有對象;
     * volatile  禁止編譯器進行指令重排
     */
    private volatile static SlackerSingleton singleton;
    /**
     * 3. 該類提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。
     */
    public static synchronized SlackerSingleton getInstance(){
        if(singleton ==null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new SlackerSingleton();
        }
        return singleton;
    }
}

4. 餓漢線程安全

餓漢(starving):該單例類非常餓,迫切需要吃東西,所以它在類加載的時候就立即創建對象。特點是加載類的時候比較慢,但運行時獲得對象的速度比較快。從加載到應用結束會一直占用資源。

package com.aaa.designmode.singleton;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 16:09
 * @description:餓漢式單例模式
 * @modified By:
 * @version:
 */
public class StarvingSingleton {
    /**
     * 1、構造器私有
     */
    private StarvingSingleton() {
    }
    /**
     * 2. 類定義中含有一個該類的靜態私有對象;
     */
    private final static StarvingSingleton singleton= new StarvingSingleton();
    /**
     * 3. 該類提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。
     */
    public static  StarvingSingleton getInstance(){
        return singleton;
    }
}

測試

	public static void main(String[] args) {
        StarvingSingleton ss1 = StarvingSingleton.getInstance();
        StarvingSingleton ss2 = StarvingSingleton.getInstance();
        System.out.println(ss1==ss2);
    }

結果為true

5. 餓漢靜態線程安全

package com.aaa.designmode.singleton;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 16:09
 * @description:餓漢式單例模式
 * @modified By:
 * @version:
 */
public class StarvingSingleton {
    /**
     * 1、構造器私有
     */
    private StarvingSingleton() {
    }
    /**
     * 2. 類定義中含有一個該類的靜態私有對象;
     */
    private final static StarvingSingleton singleton;
    /**
     * 靜態代碼塊
     */
    static {
        singleton = new StarvingSingleton();
    }
    /**
     * 3. 該類提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。
     */
    public static  StarvingSingleton getInstance(){
        return singleton;
    }
}

6. 枚舉單例模式

什么是枚舉類型?
jdk1.5之后出現的一種java類型,可以提前知道一個類的對象個數。
例如一年四季,提前知道四個季節,不會再出現第五個季節。

第一種構建枚舉類型的方式,借鑒單例(餓漢式)
package com.aaa.designmode.singleton;

/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 17:02
 * @description:季節
 * @modified By:
 * @version:
 */
public class Season {
    private  Season(){
    }
    public static final  Season Spring= new Season();
    public static final  Season Summer= new Season();
    public static final  Season Autumn= new Season();
    public static final  Season Winter= new Season();
}
package com.aaa.designmode.singleton;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 17:07
 * @description:
 * @modified By:
 * @version:
 */
public enum  SeasonEnum {
    Spring,
    Summer,
    Autumn,
    Winter
}

測試

public static void main(String[] args) {
        Season season1 = Season.Spring;
        Season season2 = Season.Spring;
        System.out.println(season1==season2);
    }

在這里插入圖片描述

7. 靜態內部類

package com.aaa.designmode.singleton;
/**
 * @author :Teacher陳
 * @date :Created in 2022/9/24 17:14
 * @description:靜態內部類單例模式
 * @modified By:
 * @version: 1.0
 */
public class StatticInnerSingleton {
    /**
     * 1、私有化構造器
     */
    private StatticInnerSingleton() {
    }
    /**
     * 2、靜態內部類
     */
    static class TempClass{
        private final static StatticInnerSingleton singleton=new StatticInnerSingleton();
    }
    /**
     * 3、公有的靜態獲取實例的方法
     */
    public static StatticInnerSingleton getInstance(){
        return TempClass.singleton;
    }
}

原文鏈接:https://blog.csdn.net/qq_60969145/article/details/127040383

欄目分類
最近更新