1. 程式人生 > >C#學習之設計模式:工廠模式

C#學習之設計模式:工廠模式

缺陷 進行 type 系列 concrete 改變 cnblogs static 優劣

  最近研究一下設計模式中工廠模式的應用,在此記錄如下:

  什麽是工廠模式?

  工廠模式屬於設計模式中的創造型設計模式的一種。它的主要作用是協助我們創建對象,為創建對象提供最佳的方式。減少代碼中的耦合程度,方便後期代碼的維護。

  工廠模式又可以詳細細分為簡單工廠模式,工廠方法模式和抽象工廠模式。下面我們一一道來。

  簡單工廠模式

  嚴格意義上來說,簡單工廠模式並不能計入設計模式大家族。

  因為它並不嚴格符合設計模式要求的對擴展開放,對修改關閉的設計原則。但是簡單工廠模式簡單好用,行之有效,在小型項目或簡單的代碼邏輯中使用它的人並不少。

  定義:

簡單工廠模式是屬於創建型模式,又叫做靜態工廠方法(Static Factory Method)模式,但不屬於23種GOF設計模式之一。簡單工廠模式是由一個工廠對象決定創建出哪一種產品類的實例。簡單工廠模式是工廠模式家族中最簡單實用的模式。

  類圖:

技術分享圖片

  其中:

  工廠(Creator)角色是 簡單工廠模式的核心,它負責實現創建所有實例的內部邏輯。工廠類的創建產品類的方法可以被外界直接調用,創建所需的產品對象。
  抽象產品(Product)角色 簡單工廠模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口。
  具體產品(Concrete Product)角色 是簡單工廠模式的創建目標,所有創建的對象都是充當這個角色的某個具體類的實例。

  通俗理解就是:Creator是工廠類,通過它進行條件選擇決定實例化哪一個具體的product,並返回接口Iproduct,客戶端直接使用Iproduct即可,無需了解Iproduct到底是什麽類。

  適用範圍和適用場景

  1. 工廠類負責創建的對象比較少;
  2. 客戶只知道傳入工廠類的參數,對於如何創建對象(邏輯)不關心;
  3. 由於簡單工廠很容易違反高內聚責任分配原則,因此一般只在很簡單的情況下應用。

  優點:

  簡單直接,易於理解,減少了客戶端的條件判斷。

  缺點:

  1.由於工廠類集中了所有實例的創建邏輯,違反了高內聚責任分配原則,將全部創建邏輯集中到了一個工廠類中;它所能創建的類只能是事先考慮到的類。

  2.如果需要添加新的類,則就需要改變工廠類了。 當系統中的具體產品類不斷增多時候,可能會出現要求工廠類根據不同條件創建不同實例的需求.這種對條件的判斷和對具體產品類型的判斷交錯在一起,很難避免模塊功能的蔓延,對系統的維護和擴展非常不利;

代碼示例

//客戶端,客戶端調用時只與簡單工廠類進行交互,客戶端不知道也不關心工廠類返回給它的是哪一個具體的類,只要知道這個類能夠完成的功能有哪些即可
    class Program
    {
        static void Main(string[] args)
        {
            string DataBaseType = "SqlServer";
            Factory factory = new Factory(DataBaseType);
            factory.IDataBase.Select();
            factory.IDataBase.Insert();

            Console.ReadKey();

        }
    }
    //數據庫接口,相當於圖中的Iproduct。通過定義接口,方便客戶端知道如何調用工廠方法返回的類
    public interface IDataBase
    {
        void Select();
        void Insert();
    }
    
    
    
    //具體的產品類,滿足接口約束之後,完成各自不同的動作,相當於圖中的product_A,product_B等
    
    public class Oracle : IDataBase
    {
        public void Insert()
        {
            Console.WriteLine("執行Oracle類的Insert");
        }

        public void Select()
        {
            Console.WriteLine("執行Oracle類的Select");
        }
    }
    
    
    public class Access : IDataBase
    {
        public void Insert()
        {
            Console.WriteLine("執行Access類的Insert");
        }

        public void Select()
        {
            Console.WriteLine("執行Access類的Select");
        }
    }
    
    public class SqlServer : IDataBase
    {
        public void Insert()
        {
            Console.WriteLine("執行sqlServer類的Insert");
        }

        public void Select()
        {
            Console.WriteLine("執行sqlServer類的Select");
        }
    }
    
    
    //簡單工廠類,通過條件分支判斷具體應該實例化哪一個類。相當於圖中的Creator
    
    public class Factory
    {
        public IDataBase IDataBase { get; set; }
        public Factory(string DbType)
        {
            switch (DbType)
            {
                case "SqlServer":
                    IDataBase = new SqlServer();
                    break;
                case "Oracle":
                    IDataBase = new Oracle();
                    break;
                case "Access":
                    IDataBase = new Access();
                    break;
            }
        }
    }


  簡單工廠模式,我非常喜歡用,它足夠簡單,代碼量小,但是有效。通常,我們國土行業內部系統中會出現類似於這樣的代碼。

class BadCode
    {
        string GetBadCode(string DataBaseType)
        {
            string Result = "";
            if (DataBaseType == "Oracle")
            {
                /*此處大量代碼(數十行)運算獲得從oracle數據庫中獲取的結果Result*/
            }
            else if (DataBaseType == "Access")
            {
                /*此處大量代碼(數十行)運算獲得從Access數據庫中獲取的結果Result*/
            }
            else if (DataBaseType == "SqlServer")
            {
                /*此處大量代碼(數十行)運算獲得從SqlServer數據庫中獲取的結果Result*/
            }
            return Result;
        }
    }

  這樣的代碼閱讀起來非常費勁,如果想要修改這樣的代碼,也是非常仿徨的。如果把每個條件分支中的代碼稍作整理封裝到類中,並提取公共接口,采用簡單工廠模式組織的話,代碼的清晰度必然有大的提升。

  工廠方法模式

  簡單工廠高效,好用,但是它只適合簡單的有固定的待創建對象列表的情況,不然反復修改工廠類中的case分支很麻煩,也不符合軟件設計的開閉原則。

  通過分析人們發現簡單工廠模式主要是工廠類與判斷分支耦合了,那麽就從這個地方下手,根據依賴倒轉原則把工廠類抽象出一個接口,將具體的實例化方法延遲到接口的子類來實現。工廠方法模式就出現了。

  定義:

定義一個用於創建對象的接口,讓子類決定實例化哪一個類,工廠方法是一個類的實例化延遲到其子類

  類圖:

技術分享圖片

  它的核心結構有四個角色,分別是抽象工廠;具體工廠;抽象產品;具體產品

  抽象工廠(Creator)角色:是工廠方法模式的核心,與應用程序無關。任何在模式中創建的對象的工廠類必須實現這個接口。

  具體工廠(Concrete Creator)角色:這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建產品對象。

  抽象產品(Product)角色:工廠方法模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。

  具體產品(Concrete Product)角色:這個角色實現了抽象產品角色所定義的接口。某具體產品有專門的具體工廠創建,它們之間往往一一對應。

  抽象工廠不再負責直接創建具體的對象,它只定義了一個生成抽象產品的接口,由其子類去生產具體的產品。這樣進一步抽象化的好處是使得工廠方法模式可以使系統在不修改具體工廠角色的情況下引進新的產品。

  簡單的說就是:用戶調用Creator接口,並實例化為一個具體的Concrete Creator,Concrete Creator返回一個Product給用戶使用。用戶無需知道Product是哪一個具體的Concrete Product。Product也不關心是誰在調用它。在需要更換Concrete Creator時,因為采用了依賴註入的原則設計,也只需要將Creator實例化為另一個Concrete Creator,其他地方無需修改。

  適用範圍和適用場景

  工廠方法模式是簡單工廠模式的衍生,解決了許多簡單工廠模式的問題。首先完全實現‘開-閉 原則’,實現了可擴展。其次更復雜的層次結構,可以應用於產品結果復雜的場合。 適用於單個產品分類多個產品的情況,或者用戶知道自己需要實例化哪一個具體的類,但是這個具體的類可能需要經常變化的情況。

  代碼示例

class Program
    {
        static void Main(string[] args)
        {
            IFactory factory = new SqlServerFactory();
            IDataBase dataBase = factory.CreateDataBase();
            dataBase.Insert();
            dataBase.Select();
            Console.ReadKey();

        }
    }
    
    public interface IFactory
    {
        IDataBase CreateDataBase();
    }
    
    public interface IDataBase
    {
        void Select();
        void Insert();
    }
    
    public class OracleFactory : IFactory
    {
        public IDataBase CreateDataBase()
        {
            return new Oracle();
        }
    }
    
    public class AccessFactory : IFactory
    {
        public IDataBase CreateDataBase()
        {
            return new Access();
        }
    }
    
    public class SqlServerFactory : IFactory
    {
        public IDataBase CreateDataBase()
        {
            return new SqlServer();
        }
    }
    
    public class SqlServer : IDataBase
    {
        public void Insert()
        {
            Console.WriteLine("執行sqlServer類的Insert");
        }

        public void Select()
        {
            Console.WriteLine("執行sqlServer類的Select");
        }
    }
    
    public class Oracle : IDataBase
    {
        public void Insert()
        {
            Console.WriteLine("執行Oracle類的Insert");
        }

        public void Select()
        {
            Console.WriteLine("執行Oracle類的Select");
        }
    }
    
    public class Access : IDataBase
    {
        public void Insert()
        {
            Console.WriteLine("執行Access類的Insert");
        }

        public void Select()
        {
            Console.WriteLine("執行Access類的Select");
        }
    }

  優點:

  1.需要增加待生成的子類對象時,只需要添加子類和相應的工廠即可,無需修改已有代碼,符合開閉設計原則。

  2.在於通過依賴註入可以很輕松的實現具體產品類的更換,而無需修改大量的代碼,如果配合反射甚至可以做到無需修改代碼。

  缺點:

  將必要的判斷移到了客戶端調用方,導致如果需要根據條件判斷初始化哪個子類的條件判斷暴露給了調用方。因此一般工廠方法模式適用於已知需要初始化的類,但此類可能經常需要變化的情況。當然這個缺陷可以通過反射來進行解決。

  抽象工廠模式

  抽象工廠模式是所有形態的工廠模式中最為抽象和最具一般性的一種形態。抽象工廠模式是指當有多個抽象角色時,使用的一種工廠模式。抽象工廠模式可以向客戶端提供一個接口,使客戶端在不必指定產品的具體的情況下,創建多個產品族中的產品對象。

  定義

為創建一組相關或相互依賴的對象提供一個接口,而且無需指定他們的具體類。

  類圖:

技術分享圖片

  核心角色為:

  抽象工廠(AbstractFactory),是抽象工廠模式的核心,與應用程序無關。任何在模式中創建的對象的工廠類必須實現這個接口。與工廠方法模式不同的是,抽象工廠模式的抽象工廠定義了至少兩組不同的抽象產品的創建方法。

  具體工廠(Concrete Creator):這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建產品對象。與工廠方法模式不同的是,抽象工廠模式的具體工廠實現了至少兩組不同的抽象產品的創建方法。

  抽象產品(AbstractProduct)角色:抽象工廠模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。
  具體產品(ConcreteProduct)角色:這個角色實現了抽象產品角色所定義的接口。某具體產品有專門的具體工廠創建,它們之間往往一一對應。

  適用範圍和適用場景

  1.當需要創建產品家族,或者需要想讓創建的產品集合起來時使用。

  2.當系列產品不固定,在以後可能會增加整個系列產品時使用。

  優點:

  抽象工廠模式的優點和工廠方法模式的優點基本類似,但是抽象工廠模式更擅長於處理多個產品線的情況,通俗的說就是當需要同時初始化兩種及以上不同類別的對象的時候,使用抽象工廠模式相對於工廠方法模式可以少寫很多具體工廠類,能夠更好的管理代碼。

  缺點:

  抽象工廠模式不是萬能的。抽象工廠模式的缺點和工廠方法模式的卻點也基本類似。但是它還有一個獨有的缺點:它特別不適用於產品系列不固定的情況,它可以很容易的添加ConcreteProduct以及Concrete Creator,但是它在添加AbstractProduct的時候需要修改大量的現有類,不符合開閉原則。

  代碼示例

/// <summary>
    /// 抽象工廠,兩條“產品線”,采礦權和探礦權
    /// </summary>
    public interface IFactory
    {
        ICKQ CreateCKQ();
        ITKQ CreateTKQ();

    }
    
    public class XLFactory : IFactory
    {
        public ICKQ CreateCKQ()
        {
            return new CKQXL();
        }

        public ITKQ CreateTKQ()
        {
            return new TKQXL();
        }
    }
    
    public class BGFactory : IFactory
    {
        public ICKQ CreateCKQ()
        {
            return new  CKQBG();
        }

        public ITKQ CreateTKQ()
        {
            return new TKQBG();
        }
    }
    
    /// <summary>
    /// 采礦權“產品線”下的“產品”
    /// </summary>
    public interface ICKQ
    {
        //簽收
        void Sign();
        //辦理完成
        void Finish();
        //保存
        void Save();
    }
    
    /// <summary>
    /// 探礦權“產品線”下的“產品”
    /// </summary>
    public interface ITKQ
    {
        void Sign();
        void Finish();
        void Save();
    }
    
    public class CKQXL : ICKQ
    {
        public void Finish()
        {
            Console.WriteLine("實現采礦權新立的辦理完成");
        }

        public void Save()
        {
            Console.WriteLine("實現采礦權新立的保存");
        }

        public void Sign()
        {
            Console.WriteLine("實現采礦權新立的簽收");
        }
    }
    
    public class CKQBG : ICKQ
    {
        public void Finish()
        {
            Console.WriteLine("實現采礦權變更的辦理完成");
        }

        public void Save()
        {
            Console.WriteLine("實現采礦權變更的辦理完成");
        }

        public void Sign()
        {
            Console.WriteLine("實現采礦權變更的辦理完成");
        }
    }
    
    public class TKQBG: ITKQ
    {
        public void Finish()
        {
            Console.WriteLine("實現探礦權變更的辦理完成");
        }

        public void Save()
        {
            Console.WriteLine("實現探礦權變更的保存");
        }

        public void Sign()
        {
            Console.WriteLine("實現探礦權變更的簽收");
        }
    }
    
    public class TKQXL:ITKQ
    {
        public void Finish()
        {
            Console.WriteLine("實現探礦權新立的辦理完成");
        }

        public void Save()
        {
            Console.WriteLine("實現探礦權新立的保存");
        }

        public void Sign()
        {
            Console.WriteLine("實現探礦權新立的簽收");
        }
    }
    
    static void Main(string[] args)
        {
            IFactory factory = new XLFactory();
            ICKQ ckq = factory.CreateCKQ();
            ckq.Save();
            ckq.Sign();
            ckq.Finish();
            ITKQ tkq = factory.CreateTKQ();
            tkq.Save();
            tkq.Sign();
            tkq.Finish();
            Console.ReadKey();

        }

三種工廠優劣勢比較及總結

  一句話來說就是:工廠方法模式針對的是一個產品等級結構;而抽象工廠模式針對的是多個產品等級結構。簡單工廠模式簡單好用,包含判斷邏輯,但不適合大型復雜情況。

  個人理解:最好用的是簡單工廠,最難懂的是抽象工廠。很多情況下,我們甚至可以將簡單工廠模式與工廠方法模式和抽象工廠模式結合起來使用,可能是一個最好的折中的方式。

代碼地址:

https://coding.net/u/wenpeng/p/DesignPattern/git

引用說明

  1. https://baike.baidu.com/item/簡單工廠模式/8801727?fr=aladdin
  2. https://baike.baidu.com/item/設計模式/1212549?fr=aladdin
  3. http://blog.csdn.net/carson_ho/article/details/52343584
  4. 《大話設計模式》 https://book.douban.com/subject/2334288/
  5. https://baike.baidu.com/item/工廠方法模式
  6. https://baike.baidu.com/item/抽象工廠模式
  7. https://www.cnblogs.com/jenkinschan/p/5712874.html

C#學習之設計模式:工廠模式