1. 程式人生 > >二十三種設計模式[1] - 工廠方法(Factory Method)

二十三種設計模式[1] - 工廠方法(Factory Method)

前言

       工廠方法,又名工廠模式,屬於建立型模式。

       其目的是通過定義一個用於建立物件的介面,讓子類決定例項化哪一個類,使一個類的例項化延遲到子類。

       所以,當你不知道你必須要建立的物件的型別時或者你希望在程式執行時決定你需要建立的型別時,可以考慮工廠方法。

結構

FactoryMethod_1

需要角色如下:

  • IProduct(產品介面):工廠方法生產的產品介面;
  • Product(產品):產品介面的實現類;
  • IFactory(工廠介面):承載了工廠方法的介面;
  • ConcreteFactory(工廠):工廠介面的實現類;

實現

       在《設計模式 - 可複用的面向物件軟體》一書中將工廠方法分為引數化工廠方法(簡單工廠)非引數化工廠方法(工廠模式)兩種。

       下面例子中,以滑鼠為例。看看如何使用引數化工廠方法和非引數化工廠方法去實現一個滑鼠的生產。

       在以下示例中,開發語言均使用C#,在採用其它語言實現時,會有些許不同。

  • 引數化工廠方法(簡單工廠)

       使用簡單工廠去實現一個產品的生產,我們首先需要抽象出這個產品的介面,再分別由不同型別的產品去實現這個介面。之後,我們還需要一個承載工廠方法的工廠類來生產這個產品,供呼叫者使用。

image

public interface IMouse
{
    string GetBrand();
}

public class LogitechMouse : IMouse
{
    public string GetBrand()
    {
        return "羅技-Logitech";
    }
}

public class RazeMouse : IMouse
{
    public string GetBrand()
    {
        return "雷蛇-Raze";
    }
}

public class MouseFactory
{
    public IMouse CreateMouse(string brand)
    {
        if (string.IsNullOrEmpty(brand))
        {
            return null;
        }

        if(brand == "羅技")
        {
            return new LogitechMouse();
        }
        else if (brand == "雷蛇")
        {
            return new RazeMouse();
        }
        else
        {
            return null;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        //建立工廠
        MouseFactory factory = new MouseFactory();

        //通過工廠生產實體
        IMouse mouseA = factory.CreateMouse("羅技");
        IMouse mouseB = factory.CreateMouse("雷蛇");

        Console.WriteLine($"MouseA的品牌是:{mouseA.GetBrand()}");
        Console.WriteLine($"MouseB的品牌是:{mouseB.GetBrand()}");
        Console.ReadKey();
    }
}

       示例中MouseFactory的CreateMouse函式就是所謂的工廠方法,呼叫者通過MouseFactory的例項呼叫工廠方法來獲取具體的滑鼠實體,工廠方法通過呼叫者的需求(入參)去生產相應型別的滑鼠實體。

       不難看出,示例中存在如下缺陷:

    1. 當呼叫者向工廠下達的需求不能夠被工廠識別時,比如傳入“羅技滑鼠”(對於滑鼠生產來講,羅技與羅技滑鼠沒有本質區別),工廠將不能正確生產對應的滑鼠實體。
    2. 當工廠增加新的滑鼠種類時,就需要在MouseFactory的CreateMouse函式中增加if else語句,不符合開閉原則(對拓展開放,對修改關閉)。

       為解決這兩個問題,做出如下修改。

/// <summary>
/// 滑鼠常數,維護滑鼠類的完全限定名
/// </summary>
public class MouseBrandConst
{
    public static readonly string Logitech = typeof(LogitechMouse).FullName;
    public static readonly string Raze = typeof(RazeMouse).FullName;
}

public class MouseFactory
{
    public IMouse CreateMouse(string brand)
    {
        if (string.IsNullOrEmpty(brand))
        {
            return null;
        }

        //if(brand == "羅技")
        //{
        //    return new LogitechMouse();
        //}
        //else if (brand == "雷蛇")
        //{
        //    return new RazeMouse();
        //}
        //else
        //{
        //    return null;
        //}

        //反射滑鼠實體
        return Activator.CreateInstance(Type.GetType(brand)) as IMouse;
    }
}

 class Program
 {
     static void Main(string[] args)
     {
         //建立工廠
         MouseFactory factory = new MouseFactory();

         //通過工廠生產實體
         //IMouse mouseA = factory.CreateMouse("羅技");
         //IMouse mouseB = factory.CreateMouse("雷蛇");
         IMouse mouseA = factory.CreateMouse(MouseBrandConst.Logitech);
         IMouse mouseB = factory.CreateMouse(MouseBrandConst.Raze);

         Console.WriteLine($"MouseA的品牌是:{mouseA.GetBrand()}");
         Console.WriteLine($"MouseB的品牌是:{mouseB.GetBrand()}");
         Console.ReadKey();
     }
 }

       增加滑鼠常數類,將呼叫者的需求(入參)規範化,防止呼叫者向工廠下達不能被工廠識別的需求。在MouseFactory的CreateMouse函式中通過反射來生產滑鼠實體。這樣,在工廠增加新的滑鼠種類時,只需要增加實現了IMouse介面的滑鼠類,並將該滑鼠類的完全限定名維護在滑鼠常數中即可。

       需要說明的是,常數類只是提供給呼叫者的一個入參模板,並不能完全限定其傳入的引數。雖然通過反射可以讓我們在增加新的產品時不必修改工廠,但這隻適用於所有產品的例項化邏輯一致的情況下,另外反射的效率要比new的效率低。

  • 非引數化工廠方法(工廠模式)

       使用工廠模式去實現一個產品的生產,我們首先需要抽象出這個產品的介面,再分別由不同型別的產品去實現這個介面。之後,我們還需要抽象出一個工廠的介面,再分別由生產不同型別產品的工廠去實現這個介面,供呼叫者使用。

image

public interface IMouse
{
    string GetBrand();
}

public class LogitechMouse : IMouse
{
    public string GetBrand()
    {
        return "羅技-Logitech";
    }
}

public class RazeMouse : IMouse
{
    public string GetBrand()
    {
        return "雷蛇-Raze";
    }
}

public interface IMouseFactory
{
    IMouse CreateMouse();
}

public class LogitechMouseFactory : IMouseFactory
{
    public IMouse CreateMouse()
    {
        return new LogitechMouse();
    }
}

public class RazeMouseFactory : IMouseFactory
{
    public IMouse CreateMouse()
    {
        return new RazeMouse();
    }
}

class Program
{
    static void Main(string[] args)
    {
        //建立工廠
        IMouseFactory logitechFactory = new LogitechMouseFactory();
        IMouseFactory razeFactory = new RazeMouseFactory();

        //通過工廠生產實體
        IMouse mouseA = logitechFactory.CreateMouse();
        IMouse mouseB = razeFactory.CreateMouse();

        Console.WriteLine($"MouseA的品牌是:{mouseA.GetBrand()}");
        Console.WriteLine($"MouseB的品牌是:{mouseB.GetBrand()}");
        Console.ReadKey();
    }
}

       示例中IMouseFactory的CreateMouse函式就是所謂的工廠方法,呼叫者通過IMouseFactory的子類去例項化IMouseFactory介面本身(多型,面向物件的基本特徵之一),再呼叫工廠方法去例項化相應滑鼠實體。

       這樣的好處是,在工廠增加新的滑鼠種類時,只需要增加實現了IMouse介面的滑鼠類和負責生產這個滑鼠的實現了IMouseFactory介面的工廠類(承載了工廠方法的類,如示例中的LogitechMouseFactory )即可。在符合開閉原則的同時避免了簡單工廠中不能完全將呼叫者傳入的引數規範化導致的工廠不能正確生產對應的滑鼠實體的問題,並且new的效率要比反射效率高。

       雖然在工廠模式中解決了簡單工廠的一些弊端,但是隨著滑鼠種類的增加,對應的工廠數量也會日益龐大。在工廠模式中,這是不可避免的一個問題。

總結

       工廠方法模式,簡單的理解就是將一系列物件的例項化邏輯封裝到一個或幾個從同一介面派生的類中,使呼叫者更專注於面向介面的開發而不必過於關心具體的實現,給予了我們很大的靈活性。工廠的指定即可以是靜態的(編譯時指定)也可以是動態的(執行時指定)。它是符合開閉原則的,方便了我們對於程式的擴充套件及維護。

       但在簡單工廠(引數化工廠方法)下,增加了程式對工廠類的依賴,一旦工廠類不能正常使用,會有較大的影響範圍。而在工廠模式(非引數化工廠方法)中,雖然減少了程式對某個工廠類的依賴,但隨著產品的增加,工廠數量也會日益龐大。

       無論是簡單工廠還是工廠模式,在產品的基數少時,使用該模式反而會增加我們的工作量。所以,並不是所有的場景下都適用。


       以上,就是我對工廠方法的理解,希望對你有所幫助。

       示例原始碼:https://gitee.com/wxingChen/DesignPatternsPractice

       系列彙總:https://www.cnblogs.com/wxingchen/p/10031592.html

       本文著作權歸本人所有,如需轉載請標明本文連結(https://www.cnblogs.com/wxingchen/p/10078547.html)