工廠模式(Factory)

先來明確一個問題,那就是有的時候,實例化這個活動不應該總是公開的進行,

也就是不要公開的使用 new 操作符,因爲,這樣容易造成耦合問題。

我們不應該針對實現編程,但是當我們在使用 new 的時候,便是針對實現編程,

而如果您要實例化的話,必須要使用 new 這個關鍵字,

很明顯,這是一個矛盾問題!!!

當然這裏的矛盾並不是說不能夠使用 new ,而是更好的使用了 new。

那麼如何來解決這樣一個矛盾問題呢?

先來看一副類圖

image

上面這副類圖反映的就是一個簡單工廠模式了,確切的說,簡單工廠不能說是一種設計模式,

其更可以被稱作是一種編程習慣,因爲我們習慣性的將一些類的實例化放到另外一個類中來完成。

這樣在代碼的可讀性上有很大的幫助,

上面的類圖反映的就是將一組相似對象(繼承自同一個類)的實例的創建放到另外一個對象中完成,

即通過 ProductFactory 這個類來按需創建對象,何爲按需創建對象呢(或者說是動態創建對象)?

這就是要求在客戶端傳入一個參數給 ProductFactory ,

然後 ProductFactory 根據這個參數來創建指定的對象實例並返回。

下面通過一個 Demo 來將上面的簡單工廠進行實現

image

下面就來看上面的這個水果例子的具體實現了

首先要有一個 Fruit 的抽象類

namespace SimpleFactory
{
    public abstract class Fruit
    {
        /// <summary>
        /// 在此僅代表性的定義一個方法
        /// </summary>

        public abstract void Display();
    }
}

然後就是繼承自 Fruit 抽象類的四個子類

using System;

namespace SimpleFactory
{
    class
Apple : Fruit
    {
        public override void Display()
        {
            Console.WriteLine("我的名字是:蘋果 ,主要特性爲養顏護膚");
        }
    }
}

 

using System;

namespace SimpleFactory
{
    class
Orange:Fruit
    {

        public override void Display()
        {
            Console.WriteLine("我的名字是:橘子 ,主要特性爲滋肝潤胃");
        }
    }
}

 

using System;

namespace SimpleFactory
{
    class
Pear:Fruit
    {
        public override void Display()
        {
            Console.WriteLine("我的名字是:梨子 ,主要特性爲滋肝潤胃");
        }
    }
}

 

using System;

namespace SimpleFactory
{
    class Banana:Fruit
    {
        public override void Display()
        {
            Console.WriteLine("我的名字是:香蕉 ,主要特性爲滋肝潤胃");
        }
    }

}

最後還有一個非常重要的類,那就是工廠類,即 FruitFactory

using System;

namespace SimpleFactory
{
    public class FruitFactory
    {
        public FruitFactory()
        {
        }

        /// <summary>
        /// 簡單工廠中必須要有一個方法來根據指定的邏輯創建實例
        /// </summary>
        /// <param name="fruitType"></param>
        /// <returns></returns>

        public static Fruit CreateFruit(string fruitType)
        {
            if (!String.IsNullOrEmpty(fruitType))
            {
                switch (fruitType)
                {
                    case "Apple":
                        return
new Apple();
                    case "Orange":
                        return
new Orange();
                    case "Pear":
                        return
new Pear();
                    case "Banana":
                        return new Banana();
                }
            }
            return null;
        }
    }
}

最後就是來看客戶端代碼和運行結果了

using System;
using SimpleFactory;

namespace SimpleFactoryTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //創建一個蘋果實例
            //並且調用這個實例的 Display 方法

            FruitFactory.CreateFruit("Apple").Display();

            //創建一個橘子實例
            //並且調用這個實例的 Display 方法

            FruitFactory.CreateFruit("Orange").Display();

            //創建一個梨子實例
            //並且調用這個實例的 Display 方法

            FruitFactory.CreateFruit("Pear").Display();

            Console.ReadLine();
        }
    }
}

image

上面寫的這個 Demo 呢,是典型的簡單工廠了,

通過簡單工廠呢,我們實現了對實例化這個活動的封裝,

因爲把所有的實例化全部放入了另外一個對象(也就是工廠)中,

同時實現了針對接口編程,而非實現,在上面的 Demo 中,這裏體現在了工廠 FruitFactory 中,

我們通過多態來實現了針對接口 Fruit 編程,而非針對具體類,如 Apple 這些來編程。

這樣,使用簡單工廠便輕鬆的解決了一開始提出的矛盾問題。

總結一下簡單工廠的優點:

簡單工廠的工廠類中包含了必要的邏輯判斷,這樣就可以根據客戶端的選擇條件來動態的實例化相關的類,

對於客戶端來說,其去除了與具體產品(比如 Apple)之間的依賴。

當然簡單工廠模式是有缺點的:

最爲明顯的就是,其違背了開-閉原則,

在上面的 Demo 中,如果我要再增加一種水果---葡萄(Grape),

那麼做法如下,首先是定義一個葡萄類 Grape,讓其繼承自 Fruit ,然後呢,您還必須修改工廠類,

因爲您必須在工廠類的 switch 中添加幾句代碼

case "Grape":
    return new Grape();

這樣就非常明顯了,因爲您已經對 FruitFactory 類進行了修改,

且這個修改是必須得,否則您就無法增加葡萄這種水果。

所以,簡單工廠是明顯的違背了開-閉原則的。

 

 

 

 

 

 

 

由於簡單工廠違背了開-閉原則,所以還必須在其上面改進一下,這樣可以得出下面要談的模式,

也就是工廠方法模式(Factory Method)

先來給出工廠方法的結構圖吧

image

上面的類圖稍微簡單了點,但是還是可以反映出一些內容的,

和簡單工廠相比的話,在工廠方法中,創建對象將不會在集中在一個類中了,

而是通過一個 FactoryMethod 方法在各個子類中實現對象的實例化,

這句話怎麼理解呢?

也就是,在上面的類圖中,

創建 ConcreteProduct 類的實例這個功能被封裝在了 ConcreteProductFactory 中,

而不是在 ProductFactory 中了,

下面再看一副稍微複雜點的類圖或許能夠幫助更好的理解

image

這一副類圖呢,更好的展現出了工廠方法的基本實現原理,

每一個具體產品類都有與其相對應的具體工廠類,而這個具體工廠類呢,

其唯一職責就是創建與其對應的具體產品類的實例。

比如 ConcreteProductFactoryA 依賴於 ConcreteProductA ,也就是隻創建 ConcreteProductA 實例。

在這裏還是先給出工廠方法的定義吧

工廠方法模式,定義了一個用於創建對象的接口,讓子類來決定要實例化哪一個類,

工廠方法讓類把實例化延遲到其子類。

下面呢,在來看一個實際的 Demo ,這個 Demo 還是延續前面介紹簡單工廠時的水果的例子

先來看類圖吧

image

從上面的類圖中可以看到,擁有一個水果抽象類 Fruit

然後是四個水果具體類,還有一個水果工廠抽象類 FruitFactory ,

然後是四個具體工廠類,分別用來創建四個水果具體類的實例。

下面就來看代碼啦

先是 Fruit 抽象類

namespace FactoryMethod
{
    public abstract class Fruit
    {
        public abstract void Display();
    }
}

然後是四個具體的水果類

using System;

namespace FactoryMethod
{
    class Apple:Fruit
    {
        public override void Display()
        {
            Console.WriteLine("我的名字是:蘋果 ,主要特性爲養顏護膚");
        }
    }
}

 

using System;

namespace FactoryMethod
{
    class Banana:Fruit
    {
        public override void Display()
        {
            Console.WriteLine("我的名字是:香蕉 ,主要特性爲滋肝潤胃");
        }
    }
}

 

using System;

namespace FactoryMethod
{
    class Orange : Fruit
    {
        public override void Display()
        {
            Console.WriteLine("我的名字是:橘子 ,主要特性爲滋肝潤胃");
        }
    }
}

 

using System;

namespace FactoryMethod
{
    class Pear : Fruit
    {
        public override void Display()
        {
            Console.WriteLine("我的名字是:梨子 ,主要特性爲滋肝潤胃");
        }
    }
}

再來看抽象工廠類 FruitFactory

namespace FactoryMethod
{
    public abstract class FruitFactory
    {
        public abstract Fruit GetFruit();
    }
}

然後就是四個具體工廠類

namespace FactoryMethod
{
    public class
AppleFactory:FruitFactory
    {
        public override
Fruit GetFruit()
        {
            return new Apple();
        }
    }
}

 

namespace FactoryMethod
{
    public class
BananaFactory : FruitFactory
    {
        public override
Fruit GetFruit()
        {
            return new Banana();
        }
    }
}

 

namespace FactoryMethod
{
    public class
OrangeFactory : FruitFactory
    {
        public override
Fruit GetFruit()
        {
            return new Orange();
        }
    }
}

 

namespace FactoryMethod
{
    public class
PearFactory : FruitFactory
    {
        public override Fruit GetFruit()
        {
            return new Pear();
        }
    }
}

所有的類的結構的代碼就在上面了,下面將要看的就是客戶端代碼和效果了

using System;
using FactoryMethod;

namespace FactoryMethodTest
{
    class Program
    {
        static void Main(string[] args)
        {
            FruitFactory factory;
            Fruit fruit;

            //首先要實例化一個蘋果工廠
            factory = new AppleFactory();
            //然後通過工廠來獲得蘋果類對象
            fruit = factory.GetFruit();
            //訪問蘋果類對象
            fruit.Display();

            //首先要實例化一個梨子工廠
            factory = new PearFactory();
          
//然後通過工廠來獲得梨子類對象
            fruit = factory.GetFruit();
           //訪問梨子類對象
            fruit.Display();

            Console.ReadLine();
        }
    }
}

image

上面呢,就是關於工廠方法模式的 Demo 的完整演示了,

下面將通過比較簡單工廠和工廠方法這兩種設計模式來體現出各自的優劣之處:

簡單工廠把全部的事情,在一個地方(類)全部處理完,而工廠方法卻不同,

其是通過創建一個框架(FruitFactory),

然後讓子類(AppleFactory 等)決定要如何實現,簡單工廠呢,其將對象的創建封裝起來了,

但是其違背開-閉原則,彈性太弱,正如前面介紹簡單工廠時,提到的加入一種水果葡萄時會造成修改類,

而工廠方法則恰恰解決了簡單工廠的這一毛病,其通過將實例化延遲到子類成功解決了簡單工廠的這一問題,

其實呢,可以這樣去理解,簡單工廠在其主要的工廠方法中,存在了邏輯判斷語言,

這是因爲,工廠方法必須根據外界給出的條件來創建符合要求的實例,

而工廠方法卻不存在這一點,也就是不需要指到外界的情況,

這是爲何呢?其實這是因爲,工廠方法將這些本來應該存在的判斷邏輯移到了客戶端代碼中,

(通過客戶的需求來選擇生成那一種水果實例,比如我的演示中生成的就是蘋果和梨子這兩種實例)

這樣的話,可以解決掉簡單工廠違背開-閉原則這一毛病,

比如,要在上面的 Demo 中再添加一種水果 Grape 的話,

使用工廠方法的話,您只需要添加一個 Grape 類,讓其繼承自 Fruit ,

然後再爲其添加一個 GrapeFactory 類,讓這個類繼承自 FruitFactory 類就 OK 了,

其餘的代碼便是在客戶端來完成了。

 

 

 

 

下面將要介紹的是抽象工廠模式(Abstract Factory)

先來看抽象工廠的大體的結構圖

image

要想明白上面的這幅類圖的話,先必須要明確一個概念,

產品族:

在上面的產品列表中呢,有兩個產品族,一個是“具體產品A--1”和”具體產品B--1“組成的一個族,

還有一個是“具體產品A--2”和“具體產品B--2”組成的一個族。

產品族就是在不同產品等級結構中,功能相關聯的產品組成的家族。

下面就來介紹抽象工廠了(有些內容生澀的話,可以看完 Demo 後回過頭來瀏覽)

下面給出的是抽象工廠的定義:

提供一個創建一系列相關或者是相互依賴對象的接口,而無需指定它們具體的類。

再來看衣服詳細的類圖

image

其實從上面這副類圖中可以看出,每一個具體的工廠,它都只負責創建一個產品族中的產品的實例,

從抽象工廠中派生出的具體工廠,這些具體工廠產生相同的產品(這些產品都繼承自同一父類),

比如,ConcreteFactory1 和 ConcreteFactory2 中的 CreateProductA 這個方法都是產生 AbstractProductA 這種類型的產品,

但是產品的實現卻是不同的,比如 ConcreteFactory1 中的 CreateProductA 實現的是產生一個 ConcreteProductA—1 產品,

而 ConcreteFactory2 中的 CreateProductA 實現的是產生一個 ConcreteProductA—2 產品,

總的來說就是不同的具體工廠產生不同的產品族,

而抽象工廠則是定義一個負責創建一組產品(也就是一個產品族)的接口,

比如上面的類圖中只存在兩個產品族,所以在抽象工廠中便只需要定義兩個接口就可以了。

下面來剖析一下抽象工廠的優點和缺點

抽象工廠的最大好處在於交換產品系列非常方便,由於具體工廠類在一個應用中只需要在初始化的時候出現一次,

這樣就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產品配置,

比如,我在應用中本來使用的是工廠 ConcreteFactory1 來生成的產品族 1 ,

而現在需求改變了,不再使用產品族 1 ,而必須使用產品族 2 時,

此時,只需要在客戶端代碼中初始化 ConcreteFactory1 的位置,

把 ConcreteFactory1 改爲 ConcreteFactory2 就可以了,這樣就成功的將產品族1 切換到了使用產品族2 上面來,

其次,抽象工廠讓具體的創建實例過程與客戶端分離,客戶端是通過他們的抽象接口操縱實例,

產品的具體類名也被具體工廠的實現分類,不會出現在客戶端代碼中。

這一點上的話,簡單工廠和工廠方法也做得很不錯,即依賴於抽象。

同時,如果需求需要擴展的話,比如,要重新增加一個產品族,這也很好辦,

只需要增加一個具體工廠,然後增加產品族就可以了,

總之是,抽象工廠很好的遵循了開--閉原則和依賴倒置原則。

下面就來看一個 Demo ,從這個 Demo 中看出抽象工廠的優點

先來展現一下具體的類圖

image

上面的類圖呢,說明的是有兩個具體工廠,一個是 Linux 控件的製造,還有一個是 Windows 控件的製造,

然後,有兩個產品族,一個是 WindowsTextBox 和 LinuxTextBox 組成的 TextBox 產品族,

還有一個就是 WindowsButton 和 LinuxButton 組成的 Button 產品族。

下面就來寫類了

先來看工廠類吧

namespace AbstractFactory
{
    public abstract class
AbstractFactory
    {
       
//在抽象工廠中,應該包含所有產品創建的抽象方法
 
       public abstract Button CreateButton();
        public abstract TextBox CreateTextBox();

    }
}

 

namespace AbstractFactory
{
    public class WindowsFactory:AbstractFactory
    {
        public override Button CreateButton()
        {
            return
new WindowsButton();
        }

        public override TextBox CreateTextBox()
        {
            return
new WindowsTextBox();
        }
    }
}

 

namespace AbstractFactory
{
    public class LinuxFactory:AbstractFactory
    {
        public override Button CreateButton()
        {
            return
new LinuxButton();
        }

        public override TextBox CreateTextBox()
        {
            return
new LinuxTextBox();
        }
    }
}

下面就給出所有的產品類

namespace AbstractFactory
{
    public abstract class
Button
    {
        public abstract void DisplayButton();
    }
}

 

using System;

namespace AbstractFactory
{
    class LinuxButton:Button
    {
        public override void DisplayButton()
        {
            Console.WriteLine("我的類型是:{0}",
                this.GetType().ToString());
        }
    }
}

 

using System;

namespace AbstractFactory
{
    class WindowsButton : Button
    {
        public override void DisplayButton()
        {
            Console.WriteLine("我的類型是:{0}",
                this.GetType().ToString());
        }
    }
}

 

namespace AbstractFactory
{
    public abstract class TextBox
    {
        public abstract void DisplayTextBox();
    }
}

 

using System;

namespace AbstractFactory
{
    class
LinuxTextBox : TextBox
    {
        public override void DisplayTextBox()
        {
            Console.WriteLine("我的類型是:{0}",
                this.GetType().ToString());
        }
    }
}

 

using System;

namespace AbstractFactory
{
    class
WindowsTextBox:TextBox
    {
        public override void DisplayTextBox()
        {
            Console.WriteLine("我的類型是:{0}",
                this.GetType().ToString());
        }
    }
}

上面就是整個 Demo 的類了,下面就是看一下 Main 函數和效果了

using System;
using AbstractFactory;

namespace AbstractFactoryTest
{
    class Program
    {
        static void Main(string[] args)
        {
            AbstractFactory.AbstractFactory factory;
            Button button;
            TextBox textBox;

           //Windows 下操作
            factory = new WindowsFactory();
            button = factory.CreateButton();
            textBox = factory.CreateTextBox();
            button.DisplayButton();
            textBox.DisplayTextBox();

            Console.WriteLine();

            //Linux 下操作
            factory = new LinuxFactory();
            button = factory.CreateButton();
            textBox = factory.CreateTextBox();
            button.DisplayButton();
            textBox.DisplayTextBox();

            Console.ReadLine();
        }
    }
}

image

從上面 Main 函數來看的話,如果你的系統本來是基於 Linux 的話,你只需更改一行代碼

factory = new WindowsFactory();
即可實現將系統更改爲 Windows ,

抽象工廠在這種情況下是非常有用的,比如,如果要實現後臺數據庫從 Oracle 轉換到 Sql Server,

則採用抽象工廠的思想實現是最好的。

下面總結一下抽象工廠的優缺點

首先,抽象工廠的話,其可以更加方便的實現交換一個產品系列,

就像上面的 Demo 中可以輕易的實現從 Linux 上轉換爲 Windows,

同時,客戶端代碼中依賴的是抽象,而非具體的實現,

但是,抽象工廠也是有缺點的,其實這個缺點也很明顯,那就是顯得過於臃腫,

上面的 Demo 儘管還只有兩個產品族,類圖就顯得有些難看了,

如果產品族一多的話,那麼總的類數是成幾倍的增加,這樣使整個結構變得過於複雜,

類的結構也會變得更爲龐大。

 

 

 

尾聲

上面呢,接連介紹了簡單工廠,工廠方法,抽象工廠,

整個工廠模式的介紹就到此告一段落了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章