1. 程式人生 > >設計模式的征途—3.工廠方法(Factory Method)模式

設計模式的征途—3.工廠方法(Factory Method)模式

上一篇的簡單工廠模式雖然簡單,但是存在一個很嚴重的問題:當系統中需要引入新產品時,由於靜態工廠方法通過所傳入引數的不同來建立不同的產品,這必定要修改工廠類的原始碼,將違背開閉原則。如何實現新增新產品而不影響已有程式碼?工廠方法模式為此應運而生。

工廠方法模式(Factory Method) 學習難度:★★☆☆☆ 使用頻率:★★★★★

一、簡單工廠版的日誌記錄器

1.1 軟體需求說明

Requirement:M公司欲開發一個系統執行日誌記錄器(Logger),該記錄器可以通過多種途徑儲存系統的執行日誌,例如通過檔案記錄或資料庫記錄,使用者可以通過修改配置檔案靈活地更換日誌記錄方式。在設計各類日誌記錄器時,M公司的開發人員發現需要對日誌記錄器進行一些初始化工作,初始化引數的攝製過程比較複雜,而且某些引數的設定有嚴格的先後次序,否則可能會發生記錄失敗。如何封裝記錄器的初始化過程並保證多種記錄器切換的靈活性是M公司開發人員面臨的一個難題。

  M公司開發人員學習了簡單工廠模式對日誌記錄器進行了設計,初始結構如下圖所示。

1.2 基於簡單工廠的程式碼實現

  M公司的程式猿按照結構圖,寫下了核心程式碼LoggerFactory的CreateLogger方法:

    // 簡單工廠方法
    public static ILogger CreateLogger(string args)
    {
        if (args.Equals("db", StringComparison.OrdinalIgnoreCase))
        {
            // 連線資料庫,程式碼省略
            
// 建立資料庫日誌記錄器物件 ILogger logger = new DatabaseLogger(); // 初始化資料庫日誌記錄器,程式碼省略 return logger; } else if(args.Equals("file", StringComparison.OrdinalIgnoreCase)) { // 建立日誌檔案,程式碼省略 // 建立檔案日誌記錄器物件 ILogger logger = new
FileLogger(); // 初始化檔案日誌記錄器,程式碼省略 return logger; } else { return null; } }

  上述程式碼省略了具體日誌記錄器類的初始化程式碼,在LoggerFactory中提供了靜態工廠方法CreateLogger(),用於根據所傳入的引數建立各種不同型別的日誌記錄器。通過使用簡單工廠模式,將日誌記錄器物件的建立和使用分離,客戶端只需要使用由工廠類建立的日誌記錄器物件即可,無須關心物件的建立過程。

  But,雖然簡單工廠模式實現了物件的建立和使用分離,仍然存在以下兩個問題:

  (1)工廠類過於龐大!包含了大量的if-else程式碼,維護和測試的難度增大不少。

  (2)系統擴充套件不靈活,如果新增型別的日誌記錄器,必須修改靜態工廠方法的業務邏輯,違反了開閉原則。

  如何解決這兩個問題,M公司程式猿苦思冥想,想要改進簡單工廠模式,於是開始學習工廠方法模式。

二、工廠方法模式介紹

2.1 工廠方法模式概述

  在簡單工廠模式中只提供一個工廠類,該工廠類需要知道每一個產品物件的建立細節,並決定合適例項化哪一個產品類。其最大的缺點就是當有新產品加入時,必須修改工廠類,需要在其中加入必要的業務邏輯,這違背了開閉原則。此外,在簡單工廠模式中,所有的產品都由同一個工廠建立,工廠類職責較重,業務邏輯較為複雜,具體產品與工廠類之間的耦合度較高,嚴重影響了系統的靈活性和擴充套件性。

  在工廠方法模式中,不再提供一個統一的工廠類來建立所有的產品物件,而是針對不同的產品提供不同的工廠,系統提供一個與產品等級結構對應的工廠等級結構

工廠方法(Factory Method)模式:定義一個用於建立物件的介面,讓子類決定將哪一個類例項化。工廠方法模式讓一個類的例項化延遲到其子類。工廠方法模式又簡稱為工廠模式,也可稱為多型工廠模式,它是一種建立型模式。  

2.2 工廠方法模式結構圖

  工廠方法模式提供一個抽象工廠介面來宣告抽象工廠方法,而由其子類來具體實現工廠方法並建立具體的產品物件。

  從圖中可以看出,在工廠方法模式結構圖中包含以下4個角色:

  (1)Product(抽象產品):定義產品的介面,是工廠方法模式所建立的物件的超類,也就是產品物件的公共父類。

  (2)ConcreteProduct(具體產品):它實現了抽象產品介面,某種型別的具體產品由專門的具體工廠建立,具體工廠和具體產品之間一一對應。

  (3)Factory(抽象工廠):抽象工廠類,聲明瞭工廠方法,用於返回一個產品。

  (4)ConcreteFactory(具體工廠):抽象工廠的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端呼叫,返回一個具體產品類的例項。

三、工廠方法版的日誌記錄器

3.1 解決方案

  M公司的程式猿學習了工廠方法之後,決定使用工廠方法模式來重構設計,其基本結構圖如下圖所示:

  其中, Logger介面充當抽象產品角色,而FileLogger和DatabaseLogger則充當具體產品角色。LoggerFactory介面充當抽象工廠角色,而FileLoggerFactory和DatabaseLoggerFactory則充當具體工廠角色。

3.2 重構程式碼

  (1)抽象產品:ILogger介面

    public interface ILogger
    {
        void WriteLog();
    }

  (2)具體產品:FileLogger和DatabaseLogger類

    public class FileLogger : ILogger
    {
        public void WriteLog()
        {
            Console.WriteLine("檔案日誌記錄...");
        }
    }

    public class DatabaseLogger : ILogger
    {
        public void WriteLog()
        {
            Console.WriteLine("資料庫日誌記錄...");
        }
    }

  (3)抽象工廠:ILoggerFactory介面

    public interface ILoggerFactory
    {
        ILogger CreateLogger();
    }

  (4)具體工廠:FileLoggerFactory和DatabaseLoggerFactory類

    public class FileLoggerFactory : ILoggerFactory
    {
        public ILogger CreateLogger()
        {
            // 建立檔案日誌記錄器
            ILogger logger = new FileLogger();
            // 建立檔案,程式碼省略
            return logger;
        }
    }

    public class DatabaseLoggerFactory : ILoggerFactory
    {
        public ILogger CreateLogger()
        {
            // 連線資料庫,程式碼省略
            // 建立資料庫日誌記錄器物件
            ILogger logger = new DatabaseLogger();
            // 初始化資料庫日誌記錄器,程式碼省略
            return logger;
        }
    }

  (5)客戶端呼叫

    public static void Main()
    {
        ILoggerFactory factory = new FileLoggerFactory(); // 可通過引入配置檔案實現
        if (factory == null)
        {
            return;
        }

        ILogger logger = factory.CreateLogger();
        logger.WriteLog();
    }

  執行結果如下圖:

  

四、藉助反射的重構版本

4.1 逃離修改客戶端的折磨

  為了讓系統具有更好的靈活性和可擴充套件性,M公司程式猿決定對日誌記錄器客戶端程式碼進行重構,使得可以在不修改任何客戶端程式碼的基礎之上更換或是增加新的日誌記錄方式。

  在客戶端程式碼中將不再使用new關鍵字來建立工廠物件,而是將具體工廠類的類名存在配置檔案(例如XML檔案)中,通過讀取配置檔案來獲取類名,再借助.NET反射機制來動態地建立物件例項。

4.2 擼起袖子開始重構

  (1)建立配置檔案

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="LoggerFactory" value="Manulife.ChengDu.DesignPattern.FactoryMethod.v2.DatabaseLoggerFactory, Manulife.ChengDu.DesignPattern.FactoryMethod" />
  </appSettings>
</configuration>

  (2)封裝一個簡單的AppConfigHelper類

    public class AppConfigHelper
    {
        public static string GetLoggerFactoryName()
        {
            string factoryName = null;
            try
            {
                factoryName = System.Configuration.ConfigurationManager.AppSettings["LoggerFactory"];
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return factoryName;
        }

        public static object GetLoggerFactoryInstance()
        {
            string assemblyName = AppConfigHelper.GetLoggerFactoryName();
            Type type = Type.GetType(assemblyName);

            var instance = Activator.CreateInstance(type);
            return instance;
        }
    }

  (2)重構客戶端程式碼

    public static void Main()
    {
        ILoggerFactory factory = (ILoggerFactory)AppConfigHelper.GetLoggerFactoryInstance();
        if (factory == null)
        {
            return;
        }

        ILogger logger = factory.CreateLogger();
        logger.WriteLog();
    }

  執行結果如下圖所示:

  

五、工廠方法的隱藏

  有時候,為了進一步簡化客戶端的使用,還可以對客戶端隱藏工廠方法,此時,在工廠類中將直接呼叫產品類的業務方法,客戶端無須呼叫工廠方法建立產品,直接通過工廠即可使用所建立的物件中的業務方法。

  (1)修改抽象工廠

    public abstract class LoggerFactory
    {
        // 在工廠類中直接呼叫日誌記錄器的業務方法WriteLog()
        public void WriteLog()
        {
            ILogger logger = this.CreateLogger();
            logger.WriteLog();
        }

        public abstract ILogger CreateLogger();
    }

  (2)修改具體工廠

    public class DatabaseLoggerFactory : LoggerFactory
    {
        public override ILogger CreateLogger()
        {
            // 連線資料庫,程式碼省略
            // 建立資料庫日誌記錄器物件
            ILogger logger = new DatabaseLogger();
            // 初始化資料庫日誌記錄器,程式碼省略
            return logger;
        }
    }

  (3)簡化的客戶端呼叫

    public static void Main()
    {
        LoggerFactory factory = (LoggerFactory)AppConfigHelper.GetLoggerFactoryInstance();
        if (factory == null)
        {
            return;
        }

        factory.WriteLog();
    }

六、工廠方法模式總結

5.1 主要優點

  • 工廠方法用於建立客戶所需要的產品,還向客戶隱藏了哪種具體產品類將被例項化這一細節。因此,使用者只需要關心所需產品對應的工廠,無須關心建立細節
  • 在系統中加入新產品時,無需修改抽象工廠和抽象產品提供的介面,也無須修改客戶端,還無須修改其他的具體工廠和具體產品,而只要加入一個具體工廠和具體產品就可以了。因此,系統的可擴充套件性得到了保證,符合開閉原則

5.2 主要缺點

  • 在新增新產品時,需要編寫新的具體產品類,還要提供與之對應的具體工廠類,系統中類的個數將成對增加,一定程度上增加了系統的複雜度
  • 由於考慮到系統的可擴充套件性,需要引入抽象層,且在實現時可能需要用到反射等技術,增加了系統的實現難度。

5.3 適用場景

  • 客戶端不知道其所需要的物件的類。在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的的工廠即可,具體的產品物件由具體工廠建立,可將具體工廠的類名儲存到配置檔案或資料庫中。
  • 抽象工廠類通過其子類來指定建立哪個物件。在工廠方法模式中,抽象工廠類只需要提供一個建立產品的介面,而由其子類來確定具體要建立的物件,利用面向物件的多型性和里氏替換原則,在程式執行時,子類物件將覆蓋父類物件,從而使得系統易於擴充套件。

參考資料

      DesignPattern

  劉偉,《設計模式的藝術—軟體開發人員內功修煉之道》

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

設計模式征途3.工廠方法Factory Method模式

上一篇的簡單工廠模式雖然簡單,但是存在一個很嚴重的問題:當系統中需要引入新產品時,由於靜態工廠方法通過所傳入引數的不同來建立不同的產品,這必定要修改工廠類的原始碼,將違背開閉原則。如何實現新增新產品而不影響已有程式碼?工廠方法模式為此應運而生。 工廠方法模式(Factory Method) 學

小菜學習設計模式工廠方法Factory Method模式

前言 設計模式目錄: 本篇目錄:   其實大家都知道,在工廠方法(Factory Method)模式之前有個簡單工廠模式,也就是靜態工廠方法(Static Factory Method)模式,在簡單工廠模式之前有個我們都熟悉的三層架構模式,那我們就上到下一層一層的來了解下。 三層架構   三

設計模式-工廠方法Factory Method

log face inf str ace 對象實例 method tee 就是 2018-1-20 by Atlas 應用場景 Template Method Pattern是在父類建立處理邏輯的大綱骨架,而在子類補充具體的處理內容。把Template Method

設計模式2——建立型——工廠相關:簡單工廠Simple factory工廠方法Factory method,抽象工廠Abstract factory

概要 這裡試圖描述23個設計模式中的兩個工廠(Factory)相關的設計模式:工廠方法(Factorymethod),抽象工廠(Abstract factory)。 注意點: 這兩個都屬於建立型設計模式。 由於這兩個設計模式都

設計模式工廠方法Factory Method

一 目的    定義一個建立物件的介面,但是讓他的子類去決定初始化哪種型別。工廠方法使得一個類能夠推遲到他的子類去初始化。二 動機    框架運用抽象類來定義和維護物件之間的關係。一個框架經常負責這些物件的建立。考慮一些這麼一個情況:一個能夠展現多個文件的應用程式的框架。在這

設計模式征途—17.模板方法Template Method模式

opened res ati 相同 rom 配置 version factor creat 在現實生活中,很多事情都需要經過幾個步驟才能完成,例如請客吃飯,無論吃什麽,一般都包含:點單、吃東西、買單等幾個步驟,通常情況下這幾個步驟的次序是:點單=>吃東西=>買單

【java設計模式】之 模板方法Template Method模式

1. 模板方法的一個例項         這一節主要來學習一下設計模式中的模板方法模式。我們先來看一個例子:假如現在老闆讓你做一個汽車的模型,要求只要完成基本功能即可,不考慮擴充套件性,那你會怎麼做呢?我們首先會根據經驗設計一個類圖:        由這個類圖可知,非

設計模式】之模板方法Template Method

模板方法的定義為:再一個操作中定義一個演算法的骨架,將演算法中的一些步驟延遲到子類去實現。模板方法允許子類在不該變演算法結構的情況下重新定義演算法的某些步驟。 Define the skeleton of an algorithm in an operation, defe

【為什麼學習模板方法Template Method模式和模板方法Template Method模式主要解決的問題】

轉載 https://blog.csdn.net/eson_15/article/details/51323902 1. 模板方法的一個例項         這一節主要來學習一下設計模式中的模板方法模式。我們先來看一個例子:假如現在老闆讓你做一個汽車的模型,要求只要完成基

C#設計模式(3)——工廠方法模式Factory Method

tor 對象 設計 黃色 post creator log clas 抽象工廠 在簡單工廠模式中通過工廠Factory獲取不同的對象,但是有一個明顯的缺點——簡單工廠模式系統難以擴展! 一旦添加新產品就不得不修改簡單工廠方法,這樣就會造成簡單工廠的實現邏輯過於復雜, 可以通

Java設計模式-工廠方法模式Factory Method

print blog -i pri cnp 三種模式 image void share 工廠方法模式(Factory Method) 工廠模式適合:凡是出現了大量的產品需要創建,並且具有共同的接口時,可以通過工廠方法模式進行創建。在以下的三種模式中,第一種如果傳入的字符串

Java 開發 設計模式 ----工廠方法模式Factory Method

工廠方法模式(Factory Method) 工廠方法模式分為三種: 11、普通工廠模式,就是建立一個工廠類,對實現了同一介面的一些類進行例項的建立。首先看下關係圖: 舉一個傳送郵件和簡訊的例子。首先,建立二者的共同介面: public interface Sender {

程式碼設計模式工廠方法模式Factory Method

特點: 工廠方法是粒度很小的設計模式,因為模式的表現只是一個抽象的方法。提前定義用於建立物件的介面,讓子類決定例項化具體的某一個類,即在工廠和產品中間增加介面,工廠不再負責產品的建立,由介面針對不同條件返回具體的類例項,由具體類例項去實現。工廠方法模式是簡單工廠模式的衍生,

深入淺出設計模式——工廠方法模式Factory Method

轉自:https://www.cnblogs.com/Bobby0322/p/4179921.html 介紹在簡單工廠模式中,我們提到,工廠方法模式是簡單工廠模式的一個延伸,它屬於Gof23中設計模式的建立型設計模式。它解決的仍然是軟體設計中與建立物件有關的問題。它可以更好的處理客戶的需求變化。 引入我們

設計模式4——工廠方法模式factory-method

一、工廠方法模式說明 layout title folder permalink categories tags pattern Factory Method

工廠方法模式Factory Method

method 客戶端代碼 console 實例化 turn 簡單工廠 actor () blog 工廠方法模式:定義一個用於創建對象的接口,讓子類來決定實例化哪一個類。工廠方法使一個類的實例化延遲到子類。 簡單工廠模式的最大有點在於工廠勒種包含了必要的邏輯判斷,根據客戶

以C/C++語法淺談二十三種設計模式——工廠模式Factory Method

0.寫在前面 在軟體開發過程中,為了提高開發效率、增強軟體執行的穩定性,降低後期專案維護的成本,我們志在追求更加高效、簡單的設計思路來引領我們的專案產品,在經過不斷的探索與總結的過程中,我們最常用的設計模式有23中,總體分為三大類,即建立型模式、結構型模式和行為型模式,具體如下:

簡單工廠模式靜態工廠方法Static Factory Method模式

    簡單工廠模式 所謂的簡單工廠模式-----------這裡百度百科裡面的解釋:簡單工廠模式是屬於建立型模式,又叫做靜態工廠方法(Static Factory Method)模式,但不屬於23

大白話工廠方法模式Factory Method

目錄 簡單工廠模式缺陷 簡單工廠模式改造 工廠方法模式定義 工廠方法模式結構 工廠方法模式分析 參考文獻 簡單工廠模式缺陷 大白話簡單工廠模式(S

C++設計模式——模板方法Template Method

模板方法(template method)設計模式用於定義一個包含許多步驟的演算法框架,允許子類重寫(覆蓋)演算法的某一個步驟而不改變演算法整體的流程和框架。 模板方法提供的流程是骨架,子類無須覆蓋模板方法本身,只需繼承即可,即模板方法無須是 virtu