天天看點

06—Java設計模式——工廠模式、政策模式、單例模式

目錄

 六大設計原則

工廠模式

1、工廠模式在Spring中的應用:

2、簡單工廠

3、方法工廠

4、抽象工廠

政策模式

政策模式與工廠模式的綜合應用

單例模式

餓漢模式

懶漢模式

雙重檢查鎖

Spring中單例模式的使用

設計模式是程式設計的一種理念、思想,不管是那種設計模式都遵循這設計模式的六大原則。如果要是要用一句話來概括的話那就是“用抽象封裝架構、用實作拓展細節”,如果用一個詞來概括的話那就是“解耦”。六大原則與設計模式我們需要遵守但是也不可矯枉過正,否則的話項目的代碼将變得雜亂。
  •  六大設計原則

  1. 單一職責:一個類隻負責具體具體領域的一個職責,而不是實作很多的功能
  2. 依賴倒置:應該面向接口程式設計而不是面向類程式設計,定義一個實體的時候通過接口定義,再通過具體的類去實作,這樣當類要替換的時候就不需要要再去改應用的地方,我們隻需要重新定義一個類即可
  3. 迪米特法則:一個類、軟體應該盡量的少于其他的類、軟體發生依賴關系
  4. 接口隔離:要實作多接口而不是把這些方法都定義都一個接口中,換句話說就是不應該讓哪些類去依賴不需要的接口。
  5. 裡氏替換原則:這個原則要求我們如果繼承父類的話不應該覆寫父類,這樣容易造成Bug,如果有需要的話可以擴充父類。這樣做是為了所有引用父類的地方必須能透明的使用其子類對象。
  6. 開閉原則:對擴充開放,對修改關閉。他也是對上述五個原則的一個概括,上述的五個原則從一定程度上來說也是為了實作這開閉原則。
  • 工廠模式

         在工廠模式中,我們在建立對象時不會對用戶端暴露建立邏輯,并且是通過使用一個共同的接口來指向新建立的對象(這一點應該就是依賴倒置原則的展現),實作了建立者和調用者分離。利用工廠模式可以降低程式的耦合性,為後期的維護修改提供了很大的便利。将選擇實作類、建立對象統一管理和控制,進而将調用者跟我們的實作類解耦。

1、工廠模式在Spring中的應用:

        Spring中的兩大特性IOC、AOP,其中的IOC就是控制翻轉,其中的一個理念就是工廠模式。在Spring中建立Bean對象的時候,使用了工廠模式,無論是通過Xml、配置類、還是注解,最終Spring的通過簡單工廠模式來建立對象。在我們使用的時候用隻需要問工廠那對應的執行個體即可,Spring工廠中拿到beanName與Class類型後就會通過動态反射技術建立一個對象将這個對象放在工廠中的一個靜态map中,使用這個靜态Map保證了單例行,避免反複建立帶來的性能損耗。

       Spring中之是以這樣做是因為利用創痛的new來建立對象造成的耦合程度太高,例如A對象依賴B、B對象依賴C、C對象有依賴于D,這樣的開發模式造成的結果就是對象滿天飛,代碼重複率高(每個調用就建立一個對象)。程式中的耦合可以分為方法間的耦合和對象間的耦合。

2、簡單工廠

簡單工廠中的角色主要分為工廠類角色、抽象産品類角色、具體産品類角色。其中的工廠類角色是這個模式的核心,他包含有一定的商業邏輯與判斷邏輯,用來組裝對象。

abstract class Audi {
    //Audi汽車的抽象類
    public Audi(){

    }
}
public class AudiA6 extends Audi {
    public AudiA6(){
        System.out.println("這裡生産的是奧迪A6");
    }
}
public class AudiA8 extends Audi{
    public AudiA8(){
        System.out.println("這裡生産的是奧迪A8");
    }
}
public class AudiFactory {
    public Audi createAudi(String type){
        switch (type){
            case "A6":
                return new AudiA6();
            case "A8":
                return new AudiA8();
            default:
                System.out.println("目前暫無該車型的的生産能力");return null;
        }
    }
}
public class CustomerTest {
    public static void main(String[] args) {
        AudiFactory audiFactory = new AudiFactory();
        Audi audiA6 = audiFactory.createAudi("A6");
        Audi audiA8 = audiFactory.createAudi("A8");
    }
}
           

3、方法工廠

工廠方法相對于簡單工廠是又對工廠抽象了一層多了一個抽象工廠的角色。在方法工廠模式中抽象工廠角色是核心,他僅負責給出具體工廠角色需要實作的那些接口而不關心具體實作過程,他是具體工廠角色必須實作的接口或是必須實作的父類。在這個模式中展現着開閉原則,當有新的産品需要生産的時,隻要按照抽象産品、抽象工廠的合同來進行生産那麼就可以被用戶端使用進而不需要修改任何已有的代碼。

産品相關的代碼與普通工廠的相同,這裡省略
public interface IAudiFactory {
    Audi createAudi();
}
public class AudiA6Factory implements IAudiFactory {
    @Override
    public Audi createAudi() {
        System.out.println("這裡是AudiA6造車場");
        return new AudiA6();
    }
}
public class AudiA8Factory implements IAudiFactory {
    @Override
    public Audi createAudi() {
        System.out.println("這裡是AudiA8造車廠");
        return new AudiA8();
    }
}
public class CustomerTest {
    public static void main(String[] args) {
        IAudiFactory audiFactoryA6 = new AudiA6Factory();
        IAudiFactory audiFactoryA8 = new AudiA8Factory();

        AudiA6 audiA6 = (AudiA6) audiFactoryA6.createAudi();
        AudiA8 audiA8 = (AudiA8) audiFactoryA8.createAudi();
    }
}
           

4、抽象工廠

抽象工廠簡單的說就是工廠的工廠,個人認為他是在工廠方法上的有一層抽象。在這裡抽象工廠可以建立建立具體工廠,具體工廠去生産具體的産品。

  • 政策模式

        政策模式就是定義一組算法,将這些算法分裝起來使的算法可以随意切換,在政策模式中分為三個角色:封裝角色、抽象政策角色、具體政策角色。封裝角色中持有封裝政策的句柄,抽象政策角色中規定了具體政策角色要實作的接口與屬性。他的優勢在于算法之間可以自由切換避免使用多重條件判斷可擴充性非常好,但是他也會造成政策類增多,所有政策多需要對外暴露的缺點。

政策組:
public interface IStrategy {
    void algorithm();
}
public class StrategyA implements IStrategy{
    @Override
    public void algorithm() {
        System.out.println("這裡是具體政策——>StrageA");
    }
}
public class StrategyB implements IStrategy{
    @Override
    public void algorithm() {
        System.out.println("這裡是具體政策——>StrageB");
    }
}
封裝角色:
public class Context {
    private IStrategy strategy;
    public Context(IStrategy iStrategy){
        this.strategy = iStrategy;
    }
    public void doStrategy(){
        strategy.algorithm();
    }
    public IStrategy getStrategy() {
        return strategy;
    }

    public void setStrategy(IStrategy strategy) {
        this.strategy = strategy;
    }
}
測試類:
public class StrageTest{
    public static void main(String[] args) {
        Context context = new Context(new StrategyA());
        context.doStrategy();
        context.setStrategy(new StrategyB());
        context.doStrategy();
    }
}
           

政策模式與工廠模式的綜合應用

在下面的代碼示例中政策與工廠的綜合應用是終歸中舉的,在實際應用中或許我們可以将CashContent作為工廠的參數傳遞進去,同時傳遞進去的還有計算的類型,這樣可以根據計算類型生産組裝CashContent

//政策組
public interface ICashStrategy {
    double calculateCash(double curCash);
}
class CashStrategyDiscount implements ICashStrategy {
    @Override
    public double calculateCash(double curCash) {
        return curCash*0.8;
    }
}
class CashStrategySpecialOffer implements ICashStrategy{
    @Override
    public double calculateCash(double curCash) {
        return curCash>100?curCash-20:curCash;
    }
}
//分裝角色
public class CashContent {
    ICashStrategy iCashStrategy = null;
    public CashContent(){}
    public CashContent(ICashStrategy cashStrategy){
        this.iCashStrategy = cashStrategy;
    }
    public ICashStrategy getiCashStrategy() {
        return iCashStrategy;
    }
    public void setiCashStrategy(ICashStrategy iCashStrategy) {
        this.iCashStrategy = iCashStrategy;
    }
    public double doStetragy(double currCash){
        return iCashStrategy.calculateCash(currCash);
    }
}
//工廠政策類
public class StategyFactory {
    public static CashContent productCashContent(String type) throws Exception {
        switch (type){
            case "滿減":
                return new CashContent(new CashStrategySpecialOffer());
            case "打折":
                return new CashContent(new CashStrategyDiscount());
            default:
                throw new Exception("展無目前活動");
        }
    }
}
//測試
    //正常情況下
    public static void main(String[] args) throws Exception {
        CashContent cashContent = new CashContent();
        String type = "滿減";
        double currCash = 100.2;
        switch (type){
            case "滿減":
                cashContent.setiCashStrategy(new CashStrategySpecialOffer());break;
            case "打折":
                cashContent.setiCashStrategy(new CashStrategyDiscount());break;
            default:
                throw new Exception("活動類型輸入錯誤");
        }
        System.out.println("最終的結算金額為:"+cashContent.doStetragy(currCash));
    }
    //借助工廠模式的方法實作
    @Test
    public void factoryStrategyTest() throws Exception {
        CashContent cashContent = StategyFactory.productCashContent("滿減");
        System.out.println("最終的結算金額為:"+cashContent.doStetragy(3920));
    }
           

除過上面的使用方法,工廠與政策的結合也可以延伸出一種政策的進階玩法,就是制定政策的class檔案,通過反射建立政策。這種方式也被用來一些進階軟體中來實作自定的方法,在自己實作的方法中是要實作固定的接口與參數,下面是一種簡單的實作:

@Test
    public void advancedStrategyTest() {
        ICashStrategy ICashStrategy = null;
        //政策的路徑
        String path = "com.example.demo.designmodel.factoryandstrategy.strategy.CashStrategySpecialOffer";
        try{
            Class strategyClass = Class.forName(path);
            Constructor strategyConstructor = strategyClass.getConstructor();
            ICashStrategy = (com.example.demo.designmodel.factoryandstrategy.strategy.ICashStrategy) strategyConstructor.newInstance();
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (NoSuchMethodException e){
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("最終的結算金額為:"+ICashStrategy.calculateCash(1000));
    }
           
  • 單例模式

适用場景: 單例模式隻允許建立一個對象,是以節省記憶體,加快對象通路速度,是以對象需要被公用的 場合适合使用,如多個子產品使用同一個資料源連接配接對象等等。如:

1. 需要頻繁執行個體化然後銷毀的對象。

2. 建立對象時耗時過多或者耗資源過多,但又經常用到的對象。

3. 有狀态的工具類對象。

4. 頻繁通路資料庫或檔案的對象。

餓漢模式

        餓漢模式的好處是線程安全,使用簡單,但是他的缺點也很明顯:每次類加載的時候就會建立,如果要是這個類并不常用就會造成資源的浪費,同時因為對象的建立是在類加載時候進行的是以餓漢模式下是不能夠傳參的。餓漢模式的線程安全是因為static修飾的屬性在類加載的時候就會進行初始化。

public class HungerSingleton {
    private HungerSingleton(){}
    private final static HungerSingleton sinleton = new HungerSingleton();
    public HungerSingleton getInstance(){
        return sinleton;
    }
}
           

懶漢模式

       利用匿名内部類可以保證線程的安全性,由于匿名内部類并不會在外部類加載的時候就加載是以他也可以避免資源的浪費。Holder的加載是發生在對getInstance方法調用的時候,jvm的機制規定了每個加載器下每個類隻會被加載一次,如果要是多個線程對此類進行初始化,那麼jvm的鎖機制就會起作用保證隻有一個可以成功。這樣的機制保證線程的安全性但是帶來的另一個問題就是如果在高并發的場景下,如果多個線程對類進行初始化,這時候就會造成較長時間的堵塞。

public class LazySingleton {
    //将構造函數私有化保證隻有他内部可以建立他
    private LazySingleton(){};
    public static LazySingleton getInstance(){
        return Holder.SINGLE_TON;
    }
    private static class Holder{
        private static final LazySingleton SINGLE_TON = new LazySingleton();
    }
}
           

雙重檢查鎖

public class DBCSingleton {
    //volatile關鍵字保證讀寫一直,同時防止指令重排序
    private static volatile DBCSingleton singleton = null;
    private DBCSingleton(){}
    public DBCSingleton getInstance(){
        //這一層的非空判斷是為了提高性能,如果沒有這個判斷,每一次對getInstance的通路都會枷鎖
        //頻繁的加鎖會極大的拖低性能
        if (singleton==null){
            synchronized (DBCSingleton.class){
                //此處的非null判斷,是為了避免DBCSingleton的多次建立打破單例
                if (singleton==null){
                    singleton = new DBCSingleton();
                }
            }
        }
        return singleton;
    }
}
           

為什麼要使用volatile關鍵字?

         指令重排序是指在執行程式時,為了提供性能,處理器和編譯器常常會對指令進行重排序,但是 不能随意重排序,不是你想怎麼排序就怎麼排序,它需要滿足以下兩個條件:

1. 在單線程環境下不能改變程式運作的結果

2. 存在資料依賴的時候不能夠進行重排序

         例如在進行一個對象建立的時候,正常的順序是配置設定記憶體空間、初始化對象、将對象指向剛配置設定的記憶體空間。但是有些編譯器為了性能,可能為對第二步、第三步進行重排序順序就形成了配置設定記憶體空間、将對象指向剛配置設定的記憶體空間、初始化對象。現在如果考慮重排序,兩個線程發生以下的調用:

Time ThreadA ThreadB
T1 檢查到singleton為空
T2 擷取鎖
T3 再次檢查到singleton為空
T4 為singleton配置設定記憶體空間
T5 将singleton指向記憶體空間
T6 檢查到singleton不為空
T7 通路singleton(此時對象還未完成初始化)
T8 初始化singleton

雖然利用單例模式的機制通常情況下可以保證單例但是如果通過反射的機制類來進行對象的擷取與建立那麼單例的模式是會被破壞的,關于反射的使用可以參考:

(3條消息) 01-Java反射的原理與使用_任天柳-CSDN部落格

Spring中單例模式的使用

下面的代碼時Spring有關Bean建立注冊的相關核心代碼,其中應該關注的核心是如果是單例,且要建立單例時候的代碼,他關于鎖機制的應用,他是将鎖加在了代碼塊上:

public class AbstractBeanFactory implements ConfigurableBeanFactory {
    //充當了Bean執行個體的緩存,實作方式和單例系統資料庫相同
    private final Map singletonCash = new HashMap();
    public Object getBean(String name)throws BeansException {
        return getBean(name,null,null);
    }

    public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{
        //對傳入的Bean name稍作處理,防止闖入的Bean name名有非法字元(或做轉碼)
        String beanName = transformedBeanName(name);
        Object bean = null;
        //手工檢測單例系統資料庫
        Object shareInstance = null;
        //使用了代碼輸定同步塊,原理和同步方法相似,但是這種的寫法效率更高
        synchronized (this.singletonCash){
            shareInstance = this.singletonCash.get(beanName);
        }
        if(shareInstance!=null){
            ......
            //傳回合适的緩存Bean執行個體
            bean = getObjectForSharedInstance(name,shareInstance);
        }else{
            ......
            //取得Bean的定義
            RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(beanName,false);
            ......
            //根據Bean定義判斷,此判斷依據通常來自于元件配置檔案中的單例屬性開關
            //<bean id = "date" class="java.util.Date" scope = "singleton"/>
            //如果是單例則做如下處理
            if(mergedBeanDefinition.isSingleton()){
                synchronized (this.singletonCash){
                    //在此檢查單例系統資料庫
                    shareInstance = this.singletonCash.get(beanName);
                    if(shareInstance==null){
                        ......
                        try{
                            //這其中展現着工廠模式的思想
                            shareInstance = createBean(beanName,mergedBeanDefinition,args);
                            //向單例系統資料庫注冊Bean執行個體
                            addSingleton(beanName,shareInstance);
                        }catch(Exception ex){
                            .......
                        }finally {
                            .......
                        }
                    }
                }
                bean = getObjectForSharedInstance(name,shareInstance);
            }else {
                //如果是非單例,每次建立都要建立一個Bean執行個體,這裡沒有想系統資料庫注冊嗎,應該沒有因為也沒有什麼意義
                //<bean id= "date" class = "java.util.Date" scope="prototype"/>
                bean = createBean(beanName,mergedBeanDefinition,args);
            }
        }
        .........
        return bean;
    }

}
           

SpringMVC的Controller層預設是單例的是以在使用的時候,不要使用非靜态的成員變量這樣會造成代碼的混亂: 

06—Java設計模式——工廠模式、政策模式、單例模式

 在Spring中單例對象在容器建立的時候建立,容器隻要一直存在對象就會一直存在,當容器銷毀是對象也會銷毀,總之就是對象與容器共存亡(但是也分餓漢模式與懶漢模式,這兩者是有差別的)。多例對象是當我們使用對象的是Spring容器為我們建立,在對象使用的過程中對象就一直存在,當對象長時間不用的時候就會由JVM垃圾收集器進行收集,對象死亡。

工具類的技術選型:如果工具類中沒有配置資訊之類的話就使用靜态類,如果要是存在配置資訊(例如有過個資料源)這時候使用單例模式的好——(什麼叫有配置資訊?使用單例的模式是為了避免配置資訊相關對象的建立嗎?)

繼續閱讀