1. 程式人生 > >Java設計模式學習筆記(二) 簡單工廠模式

Java設計模式學習筆記(二) 簡單工廠模式

前言

本篇是設計模式學習筆記的其中一篇文章,如對其他模式有興趣,可從該地址查詢設計模式學習筆記彙總地址

正文開始...

1. 簡介

簡單工廠模式不屬於GoF23中設計模式之一,但在軟體開發中應用也較為頻繁,通常做為學習其他工廠模式的入門.

接下來我們從一個虛構的業務場景遇到的問題開始,到如何使用簡單工廠模式去解決這個業務場景的問題的角度,來介紹這個模式.

2. 具體業務

有一個圖表類,可以在例項化的時候根據傳入的引數建立不同的圖表型別,比如柱狀圖、餅狀圖、折線圖等等.

2.1 業務程式碼


/**

 * @author liuboren

 * @Title: 圖示類

 * @Description: 根據不同的引數,建立不同的圖表

 * @date 2019/7/12 14:31

 */

public class Chart {



    private String type;



    public Chart(Object[][] obj,String type) {

        this.type = type;

        if(type.equalsIgnoreCase("histogram")){

            //初始化柱狀圖

        }else if(type.equalsIgnoreCase("pie")){

            //初始化柱狀圖

        }else if(type.equalsIgnoreCase("line")){

            //初始化折線圖

        }

    }



    public void display(){

        if(this.type.equalsIgnoreCase("histogram")){

            // 顯示柱狀圖

        }else if(this.type.equalsIgnoreCase("pie")){

            //顯示餅狀圖

        }else if(this.type.equalsIgnoreCase("Line")){

            //顯示折線圖

        }

    }



}

客戶端程式碼通過呼叫Chart類的建構函式來建立圖表物件,根據引數type的不同可以得到不同型別的圖表,然後再呼叫display()方法來顯示相應的圖表.

2.2 問題

上述程式碼主要有以下五個問題

  • 過多的"if...else.."不易維護且影響效能
  • 違反了單一職責
  • 違反了開閉原則
  • 與客戶端耦合度高
  • 程式碼重複問題

詳細的看看以上的問題

2.2.1 過多的"if...else.."不易維護且影響效能

在Chart類中包含很多"if...else..."程式碼塊,整個類的程式碼相當冗長,程式碼越長,閱讀難度、維護難度和測試難度也越大;而且大量條件語句的存在還將影響系統的效能,程式在執行過程中需要做大量的判斷

2.2.2 違反了單一職責

Chart類的職責過重,它負責初始化和顯示所有的圖表物件, 各種圖表物件的初始化程式碼和顯示程式碼集中在一個類中實現,違反了"單一職責原則",不利於類的重用和維護;

而且將大量的物件初始化程式碼都寫在建構函式中將導致建構函式非常龐大,物件在建立時需要進行條件判斷,降低了物件建立的效率

2.2.3 違反了開閉原則

當需要增加新型別的圖表時,必須修改Chart的原始碼,違反了"開閉原則".

2.2.4 與客戶端耦合度高

客戶端只能通過new關鍵字來直接建立Chart物件,Chart類與客戶端類耦合度較高,物件的建立和使用無法分類.

2.2.5 程式碼重複問題

客戶端在建立Chart物件之前可能還需要進行大量初始化設定,例如設定柱狀圖的顏色、高度等,如果在Chart類的建構函式中沒有提供一個預設設定,那就只能由客戶端來完成初始設定,這些程式碼在每次建立Chart物件時都會出現,導致程式碼的重複.

3. 簡單工廠模式

使用簡單工廠模式,可以在一定程度上解決.

3.1 簡單工廠的基本流程

  1. 首先將需要建立的各種不同物件(例如各種不同的Chart物件)的相關程式碼封裝到不同的類中,這些類稱為具體產品類,而將他們公共的程式碼進行抽象和提取後封裝在一個抽象產品類中,每一個具體產品類都是抽象產品類的子類

  2. 然後提供一個工廠類用於建立各種產品,在工廠類中提供一個建立產品的工廠方法,該方法可以根據所傳入的引數不同建立不同的具體產品物件.

  3. 客戶端只需呼叫工廠類的工廠方法並傳入相應的引數即可得到一個產品物件

3.2 定義

定義一個工廠類,它可以根據引數的不同返回不同類的例項,被建立的例項通常都具有相同的父類.

因為在簡單工廠模式中用於建立例項的方法是靜態(static)方法,因此簡單工廠模式又被成為靜態工廠方法(Static Factory Method)模式,他屬於類建立型模式.

3.3 要點

當你需要什麼,只需要摻入一個正確的引數,就可以獲取你所需要的物件,而無需知道其建立細節.

簡單工廠模式結構比較簡單,其核心是工廠類的設計.

3.4 結構圖

3.5 角色

工廠模式結構圖包含以下幾個角色

  • Factory(工廠角色)
  • Product(抽象產品角色)
  • ConcreteProduct(具體產品角色)

3.5.1 Factory(工廠角色)

工廠角色即工廠類,它是簡單工廠模式的核心,負責實現建立所有產品例項的內部邏輯.

工廠類可以被外界直接呼叫,建立所需的產品物件.

在工廠類中提供了靜態的工廠方法factoryMethod(),它的返回型別為抽象產品型別Product

3.5.2 Product(抽象產品角色)

它是簡單工廠模式的建立目標,所有被建立的物件都充當這個角色的某個具體類的例項.

每個具體產品角色都繼承了抽象產品角色,需要實現在抽象產品中宣告的抽象方法.

3.5.3 ConcreteProduct(具體產品角色)

它是簡單工廠模式的建立目標,所有被建立的物件都充當這個角色的某個具體類的例項.

每一個具體角色都集成了抽象產品角色,需要實現在抽象產品中宣告的抽象方法.

3.6 類設計

在簡單工廠模式中,客戶端通過工廠類來建立一個產品類的例項,而無需用new關鍵字來建立物件,它是工廠模式家族中最簡單的一員

3.6.1Product類

將所有產品公共的程式碼移至抽象產品類,並在抽象長品類中宣告一些抽象方法,以供不同的具體產品來實現.

/**

 * @author liuboren

 * @Title: 產品抽象類

 * @Description: 抽離公共方法和抽象業務方法

 * @date 2019/7/12 16:38

 */

public abstract class AbstractProduct {

    //所有產品類的公共業務方法

    public void methodSame(){

        // 公共方法的實現

    }



    //宣告抽象業務方法

    public abstract void methodDiff();

}

3.6.2 Product實現類


class  ConcreteProductA extends AbstractProduct{



    @Override

    public void methodDiff() {

        //業務方法的實現

    }

}



class  ConcreteProductB extends AbstractProduct{



    @Override

    public void methodDiff() {

        //業務方法的實現

    }

}


3.6.3 工廠類


class Factory{

    public static AbstractProduct getProduct(String arg){

        AbstractProduct product = null;

        if(arg.equalsIgnoreCase("A")){

            product = new ConcreteProductA();

        }else if(arg.equalsIgnoreCase("B")){

            product = new ConcreteProductB();

        }

        return product;

    }

}

3.6.4 客戶端類


class Client{

    public static void main(String[] args) {

        AbstractProduct product;

        product = Factory.getProduct("A");

        product.methodSame();

        product.methodDiff();



    }

}

4. 使用簡單工廠模式解決業務問題

使用簡單工廠模式解決上面業務的問題

4.1 類結構圖

4.2 程式碼

Chart類:



/**

 * @author liuboren

 * @Title: 圖形介面

 * @Description:

 * @date 2019/7/15 9:42

 */

public interface Chart {



    public void display();

}

HistogramChart:


/**

 * @author liuboren

 * @Title: 柱狀圖

 * @Description:

 * @date 2019/7/15 9:44

 */

public class HistogramChart implements Chart{

    public HistogramChart() {

        System.out.println("建立了柱狀圖");

    }



    @Override

    public void display() {

        System.out.println("顯示了柱狀圖");

    }

}


PieChart:


/**

 * @author liuboren

 * @Title: 餅狀圖

 * @Description:

 * @date 2019/7/15 9:45

 */

public class PieChart implements Chart{



    public PieChart() {

        System.out.println("建立了餅狀圖");

    }



    @Override

    public void display() {

        System.out.println("顯示了餅狀圖");

    }

}


LineChart:




/**

 * @author liuboren

 * @Title: 折線圖

 * @Description:

 * @date 2019/7/15 9:47

 */

public class LineChart implements Chart {



    public LineChart() {

        System.out.println("建立了折線圖");

    }



    @Override

    public void display() {

        System.out.println("顯示了折線圖");

    }

}


ChartFactory:




/**

 * @author liuboren

 * @Title: 簡單工廠類

 * @Description:

 * @date 2019/7/15 9:48

 */

public class ChartFactory {



    public static Chart getChart(String type) {

        Chart chart = null;

        if ("histogram".equalsIgnoreCase(type)) {

            chart = new HistogramChart();

            System.out.println("初始化設定柱狀圖");

        } else if ("pie".equalsIgnoreCase(type)) {

            chart = new PieChart();

            System.out.println("初始化設定餅狀圖");

        } else if ("line".equalsIgnoreCase(type)) {

            chart = new LineChart();

            System.out.println("初始化設定折線圖");

        }

        return chart;

    }

}

Client:


/**

 * @author liuboren

 * @Title: 客戶端類

 * @Description:

 * @date 2019/7/15 9:51

 */

public class Client {

    public static void main(String[] args) {

        Chart chart = ChartFactory.getChart("pie");

        chart.display();

    }

}

4.3 優化

在兩個方面可以進行優化:

  1. 抽取客戶端的引數到配置檔案
  2. 將Chart介面和工廠類合併為一個抽象類

springboot專案可以將引數抽取到yml檔案中,使用@value註解注入,不再擴充套件了.

合併後的的UML類圖:

5. 簡單工廠模式總結

從優點、缺點及使用場景三個方面進行總結

5.1 優點

  • 物件建立和使用的分離
  • 減少冗餘類名記憶
  • 通過抽取引數到配置檔案提高靈活性

5.1.1 物件建立和使用的分離

工廠類包含必要的判斷邏輯,可以決定什麼時候建立哪一個工廠類的例項,客戶端可以免除直接建立產品物件的職責,而僅僅"消費"產品,簡單工廠模式實現了物件建立和使用的分離

5.1.2 減少冗餘類名記憶

客戶端無須知道所建立的具體產品類名,只需要知道具體產品類所對應的引數即可,對於一些複雜的類名,通過簡單工廠模式可以再一定程度減少使用者的記憶量.

5.1.3 通過抽取引數到配置檔案提高靈活性

通過引入配置檔案,可以再不修改任何客戶端程式碼的情況下更換和增加新的具體產品類,自義定程度上提高了系統的靈活性.

5.2 缺點

  • 工廠類職責過重
  • 增加系統的複雜度和理解難度
  • 系統擴充套件困難
  • 靜態方法無法繼承使用

5.2.1 工廠類職責過重

由於工廠類集中了所有產品的建立邏輯,職責過重,一旦不能正常工作,整個系統都要受到影響

5.2.2 增加系統的複雜度和理解難度

使用簡單工廠模式勢必會增加系統中類的個數(引入新的工廠類),增加了系統的複雜度和理解難度.

5.2.3 系統擴充套件困難

一旦增加新產品就不得不修改工廠邏輯,在產品型別較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴充套件和維護.

5.2.4 靜態方法無法繼承使用

簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構.

5.3 使用場景

  1. 工廠類負責建立的物件比較少,由於建立的物件比較少,不會造成工廠方法中的業務邏輯太過複雜.

  2. 客戶端只知道傳入工廠類的引數,對於如何建立物件並不關心.

6. 相關檔案

簡單工廠模式github倉庫

UML類