65.9K
CodeProject 正在变化。 阅读更多。
Home

工厂方法模式之旅

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.37/5 (9投票s)

2018 年 9 月 28 日

CPOL

11分钟阅读

viewsIcon

12885

downloadIcon

47

用各种方法说明工厂方法模式

灵感

当我第一次接触工厂方法模式时,它看起来很简单。由于没有文章具有确凿的真实性,我浏览了许多网站,发现每个人都在使用不同的实现方式,这让我感到困惑。

我脑海中开始出现很多问题,例如:在哪些场景下应该使用它?有没有固定的代码来实现它?工厂模式和工厂方法模式有什么区别,或者它们是相同的吗?如果对象的实例化从 UI 代码推迟到子类,那么实例化究竟应该发生在工厂内部还是产品内部?

在名为FACTORY的设计模式名称中一直存在很多混淆,有人说工厂,有人说简单工厂、工厂模式、工厂方法、工厂方法设计模式、抽象工厂。

因此,我决定在一个文章中使用相同的示例进行所有说明,这样就可以轻松地比较不同的概念。

引言

这是“四人帮”1定义的创建型设计模式中的第二种设计模式。

此模式在于将对象实例化推迟到子类。

GoF 没有定义“工厂模式”,但许多网站 (3, 4, 5, 6, 7, 8)确认了它的存在,因为它非常有用且被广泛使用,它是由“Garry Woodfine” (2, 3)定义的。因此,在这里我将尝试描述所有与工厂方法相关的实现方法。

谁应该阅读本文?

  1. 如果您对设计模式是新手。
  2. 如果您对工厂方法模式是新手。
  3. 如果您对工厂模式和工厂方法模式感到困惑。
  4. 如果您阅读了许多关于工厂方法的示例,但仍然感到困惑,因为您无法将代码与类图进行映射。
  5. 如果您想了解模式中的每个类做什么以及如何做。

关于本文

本文将从现实世界的场景切换到编程世界,因此故事会在两种语言之间切换。由于我尝试将代码语言与现实生活语言进行映射,您可能需要通过仔细阅读和比较代码与解释来理解。有时,您可能会觉得代码可以缩短,但同样,这只是为了与现实世界的例子进行映射。有时,您会发现两个不同阶段的同一个类没有变化,但这是故意的,以便您能够识别出变化发生在哪里。

代码中提供了足够多的代码注释,描述了代码的用途。希望您能享受这次旅程。

现实世界中的一个例子 - 榨汁机/搅拌机/研磨机

故事从厨房开始,这是每个人都需要的地方!既然是关于为日常厨房的混合产品问题寻找正确的解决方案,那将是一个漫长的过程。如果您想一气呵成,这里不是合适的地方。

目录

第一阶段 - 没有接口的简单类

现实世界场景

几年前,我很少想吃煎蛋卷,所以只用勺子搅拌鸡蛋。到目前为止一切都很好。我需要多想一下吗,可能不需要?

是的……有时候,我也需要混合果泥或果酱,但这些也可以用一个更大的罐子或杯子和勺子来完成。

我本可以买一台搅拌机,但对于这种情况来说,我不确定是否经济。

用编程术语来说

所以,有时候,如果我们(作为主类)直接实例化一个对象而不委托责任,如果它是一次性的需求,那就足够了。

类图

实现代码

UIClass.cs

using System;

namespace SimpleClass
{
    /// <summary>
    /// This is very first example of class instantiation, 
    /// no inheritance or interface concept being used here.
    /// Observations -
    ///       UI - Main class calling 2 different class for Egg and Pulp  
    ///       Product - EggProduct, PulpProduct
    /// Problem - Whenever a new product is added, separate process need to be added
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter item to blend : Egg or Pulp");
            string strUserInput = Console.ReadLine();
           
            switch (Console.ReadLine())
            {
                case "Egg":
                    // EggCup egg = new EggCup();
                    UseCupForEgg egg = new UseCupForEgg();
                    egg.BlendNMixEgg("EggLiquid");
                    break;
                case "Pulp":
                    UseJarForPulp puree = new UseJarForPulp();
                    puree.MixMangoPulp("MilkShake");
                    break;
            }
 
            Console.ReadKey();
        }
 
        public class UseCupForEgg
        {
            public void BlendNMixEgg(string readyProduct)
            {
                Console.WriteLine(readyProduct + "Egg is mixed");
            }
        }
 
        public class UseJarForPulp
        {
            public void MixMangoPulp(string readyProduct)
            {
                Console.WriteLine(readyProduct + "Mango shake is prepared.");
            }
        }
    }
}

对第一阶段的观察

我观察到的是,我总是对混合相似的东西执行相同的过程,并且最终产品的类型是相同的,那么为什么我不能对这两种过程使用相同的器具?我能否优化和改进事物?毕竟,将要用于获得相同类型的最终产品(液体)的定义过程(混合)是相同的。

第二阶段 - 没有工厂但有定义的接口

现实世界场景

所以,现在,我有一个罐子用于混合过程,将鸡蛋或果泥转化为液体。

用编程术语来说

单一 UI 使用相同过程将多种原材料转化为相同的抽象产品。

类图

实现代码

UIClass.cs

using System;
 
/// <summary>
/// This is enhanced version of class instantiation by introducing product interface
/// implementation for reusability and interaction of common functionalities.
/// Observations -  
///     1. Object instantiation of product
///     2. UI - Main class calling 2 different class for Egg and Pulp but calling same methods 
/// What's new here -
///     1. UI - Unified UI(Utencil) calling same method(BlendProduct)
/// Problem -
///     1. Every time UI code need to change while adding a new item
///     2. OCP violation
/// </summary>
namespace NoFactory
{
 
    /// <summary>
    /// This is UI code instantiating product classes
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter item for Mixer : Egg, Pulp");
 
            IMixedProduct endProduct = null;
            switch (Console.ReadLine())
            {
                case "Egg":
                    endProduct = new EggProduct();
                    endProduct.BlendProduct("EggLiquid");
                    break;
                case "Pulp":
                    endProduct = new PureeProduct();
                    endProduct.BlendProduct("MilkShake");
                    break;
            }
            Console.ReadKey();
        }
    }
}

Product.cs

/// <summary>
/// Observations -  
///     1. Products - (Two) EggProduct, PulpProduct
/// What's new here -
///     1. Product - Unified abstract product defines same method to be used by different child products
/// </summary>
 
namespace NoFactory
{
    /// <summary>
    ///  Its an essential function of blender, no matter what kind of product its going to mix
    /// </summary>
    public interface IMixedProduct
    {
        void BlendProduct(string readyProduct);
    }
 
    /// <summary>
    ///  Concrete product egg type inheriting abstract product
    /// </summary>
    public class EggProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is prepared.");
        }
    }
 
    /// <summary>
    ///  Concrete product puree type inheriting abstract product
    /// </summary>
    public class PureeProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is prepared.");
        }
    }   
}

第二阶段存在的问题

过了一段时间,我需要为家人更频繁、更大批量地制作煎蛋卷。因此,在数量和频率方面的需求增加了,我花费了更多时间来混合许多鸡蛋。

此外,我还需要制作奶昔或偶尔打成番茄泥。因此,每当我需要研磨其他物品时,要么需要修改同一个器具,要么需要使用不同的器具(即 UI)。

让我们通过代码来理解。

UIClass.cs

using System;
 
/// <summary>
/// This is enhanced version of class instantiation by introducing product interface
/// implementation for reusability and interaction of common functionalities.
/// Observations -  
///     1. Object instantiation of product
///     2. UI - Main class calling 2 different class for Egg and Pulp but calling same methods 
/// What's new here -
///     1. UI - Unified UI(Utencil) calling same method(BlendProduct)
/// Problem -
///     1. Every time UI code need to change while adding a new item
///     2. OCP violation
/// </summary>
namespace ProblemWithNoFactory
{
    /// <summary>
    /// This is UI code instantiating product classes
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter item for Mixer : Egg, Pulp");
 
            IMixedProduct endProduct = null;
            switch (Console.ReadLine())
            {
                case "Egg":
                    endProduct = new EggProduct();
                    endProduct.BlendProduct("EggLiquid");
                    break;
                case "Pulp":
                    endProduct = new PureeProduct();
                    endProduct.BlendProduct("MilkShake");
                    break;
                // New items to be processed
                case "Sauce":
                    endProduct = new SauceProduct();
                    endProduct.BlendProduct("Sauce");
                    break;
                case "Puree":
                    endProduct = new PulpProduct();
                    endProduct.BlendProduct("Puree");
                    break;
            }
            Console.ReadKey();
        }
    }
}

Product.cs

using System;

/// <summary>
/// Observations -  
///     1. Products - (Four) EggProduct, PulpProduct, SauceProduct, PulpProduct
/// What's new here -
///     1. Product - Unified abstract product defines same method to be used by different child products
/// </summary>
 
namespace ProblemWithNoFactory
{
    /// <summary>
    ///  Its an essential function of blender, no matter what kind of product its going to mix
    /// </summary>
    public interface IMixedProduct
    {
        void BlendProduct(string readyProduct);
    }
 
    /// <summary>
    ///  Concrete product egg type inheriting abstract product
    /// </summary>
    public class EggProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is prepared.");
        }
    }
 
    /// <summary>
    ///  Concrete product puree type inheriting abstract product
    /// </summary>
    public class PureeProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is prepared.");
        }
    }
 
    // New items to be added
    public class SauceProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is prepared.");
        }
    }
 
    public class PulpProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is prepared.");
        }
    }
}

此处注意点

违反 OCP(开放封闭原则)- 类应该对扩展开放,对修改关闭,但这里不是。

需求分析 -(需要是发明的母亲)

  1. 我需要一个设备,可以快速混合各种半液体物品。
  2. 我不想每次都更换混合器具。
  3. 我希望得到相同形式的输出。

第三阶段 - 简单工厂(也称为工厂、工厂模式、工厂设计模式)

解决方案(在现实世界场景中)

因此,我考虑购买一台搅拌机,它可以帮助我在更短的时间内混合更多的鸡蛋。我也可以用它来混合任何果泥制作酱料,以及混合芒果泥制作奶昔。所以它只适用于混合半液体物品。

用编程术语来说

在这里,我们将对象创建的责任委托给工厂(搅拌机)。我们将修改示例以遵循 OCP 和 SRP(单一类承担单一类型的责任)。

定义

“它是一个类,其方法负责根据其接收的输入类型创建对象”(在此示例中,即半液体 - 鸡蛋、果泥、芒果泥等)。

简而言之,工厂有助于将所有对象创建集中在一个地方,并避免在新键值对散布在整个代码库中。(3)

它创建对象而不将实例化逻辑暴露给客户端,并通过通用接口引用新创建的对象。 (8)

注意事项

  • 它可能有一个工厂接口,也可能没有,但这并非强制。
  • 工厂类可能有一个static方法,也可能没有,这取决于如何在 UI 中调用工厂。

类图

实现代码

UIClass.cs

using System;

/// <summary>
/// Observations -
///     1. Object instantiation of product of factory not the products 
/// What's new here -
///     1. UI - Main class calling a factory not 2 different class for Egg and Pulp
///     2. UI code will not be changed, while using new items,
///     but it has to be of same type which is semi-liquid
///     3. Follows - OCP, SRP
/// Problem - Limited to handle one type of item only
/// </summary>
namespace SimpleFactoryPattern
{
    /// <summary>
    /// UI class
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter item for Mixer : Egg, Pulp, Sauce, Puree");
 
            BlenderFactory liquid = new BlenderFactory();
            liquid.GetMixingProduct("SemiLiquid", Console.ReadLine());
 
            Console.ReadKey();
        }
    }
}

Factory.cs

   /// <summary>
    /// A Factory with the responsibility of object instantiation
    /// Observation -
    ///     1. UI code will not be changed, in case product is changed
    ///     2. Single class handling instantiation of child product
    ///     3. Object instantiation of product
    /// </summary>
namespace SimpleFactoryPattern
{ 
    /// <summary>
    /// Single factory handling instantiation of all products of same type
    /// </summary>
    public class BlenderFactory
    {
        public string GetMixingProduct(string inputType, string itemName)
        {
            string readyProduct = null;
            if (inputType == "SemiLiquid")
            {
                switch (itemName)
                {
                    case "Egg":
                        IMixedProduct egg = new MixtureProduct();
                        egg.BlendProduct(itemName);
                        break;
                    case "Pulp":
                        IMixedProduct pulp = new MixtureProduct();
                        pulp.BlendProduct(itemName);
                        break;
                    case "Sauce":
                        IMixedProduct sauce = new MixtureProduct();
                        sauce.BlendProduct(itemName);
                        break;
                    case "Puree":
                        IMixedProduct puree = new MixtureProduct();
                        puree.BlendProduct(itemName);
                        break;
                }
            }
            return readyProduct;
        }
    }
}

Product.cs

/// Observations -  
///     1. Products - (One) MixtureProduct
/// What's new here -
///     1. Product - Unified product implements method defined in interface
/// </summary>
namespace SimpleFactoryPattern
{
    /// <summary>
    ///  Its an essential function of blender, no matter what kind of product its going to mix
    /// </summary>
    public interface IMixedProduct
    {
        void BlendProduct(string readyProduct);
    }
 
    /// <summary>
    /// End product type implementing method defined in interface
    /// </summary>
    public class MixtureProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is mixed.");
        }
    }
}

第三阶段存在的问题

到目前为止一切都很好,但有一天我赶时间,需要捣碎煮熟的番茄来做咖喱。

需求分析 -(欲望永无止境)

我绞尽脑汁——我可以捣碎煮熟的土豆,那么我应该买一个搅拌机吗,也许现在还为时过早,让我先考虑一个更便宜、更经济的选择。我比较了搅拌机和榨汁机的相似属性,它们都有刀片和电机,并且都有混合物品的功能,并且都能产生相似的液体输出。

第四阶段 - 具有多种职责的单一工厂(不推荐)

解决方案(在现实世界场景中)

所以我用手捣碎了番茄,然后用搅拌机混合,结果奏效了,成品没有达到所需的精细度,但满足了基本需求,即捣碎的番茄。

用编程术语来说

在这里,我们调用了额外的 BOILED(煮熟的)物品的处理方法,以便在使用abstract方法之前对其进行准备。

类图

实现代码

UIClass.cs

using System;
 
namespace FactoryWithMultipleResponsibility
{
    /// <summary>
    /// Observations -
    ///     1. Object instantiation of factory not the products 
    /// What's new here -
    ///     1. UI - Main class calling factory not 2 classes for liquid and boiled item type
    /// Problem -
    /// 1. UI class with multiple type of responsibility
    /// 2. UI code will be changed, if additional responsibility comes in scope
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter item for Mixer : Liquid, or Boiled for mixing.");
            string itemName = Console.ReadLine();
 
            switch (itemName)
            {
                case "Liquid":
                    BlenderFactory liquid = new BlenderFactory();
                    liquid.GetMixingProduct("SemiLiquid", itemName);
                    //BlenderFactory.GetMixingProduct("SemiLiquid", itemName);
                    break;
                case "Boiled":
                    BlenderFactory mash = new BlenderFactory();
                    mash.GetMixingProduct("Boiled", itemName);
                    //BlenderFactory.GetMixingProduct("SemiLiquid", itemName);
                    break;
            }
 
            Console.ReadKey();
        }
    }
}

Factory.cs

/// <summary>
/// A Factory with the responsibility of object instantiation
/// Observation -
///     1. Factory need to inherit MashedProduct for one of its two input types
/// Problem -
///     1. Breaking SRP - As per concept its breaking SRP - So here you can see, 
///     there are more than 1 reason to change the class
///     Whenever a new type like solid type is added, factory code need to be changed.
/// </summary>
namespace FactoryWithMultipleResponsibility
{
    /// <summary>
    /// Single factory with multiple responsibility
    /// </summary>
    public class BlenderFactory : MashedProduct
    {
        public string GetMixingProduct(string inputType, string itemName)
        {
            string readyProduct = null;
            if (inputType == "SemiLiquid")
            {
                switch (inputType)
                {
                    case "Egg":
                        IMixedProduct egg = new MixtureProduct();
                        egg.BlendProduct(itemName);
                        break;
                    case "Pulp":
                        IMixedProduct puree = new MixtureProduct();
                        puree.BlendProduct(itemName);
                        break;
                }
            }
            if (inputType == "Boiled")
            {
                switch (inputType)
                {                   
                    case "BoiledTomato":
                        // Additional responsibility for mashing boiled items
                        MashedProduct mashTomato = new MashedProduct();
                        mashTomato.MashProduct(itemName);
 
                        IMixedProduct boildedPotato = new MixtureProduct();
                        boildedPotato.BlendProduct(itemName);
                        break;
                    case "BoiledPotato":
                        // Additional responsibility for mashing boiled items
                        MashedProduct mashPotato = new MashedProduct();
                        mashPotato.MashProduct(itemName);
 
                        IMixedProduct boildedTomato = new MixtureProduct();
                        boildedTomato.BlendProduct(itemName);
                        break;
                }
            }
            return readyProduct;
        }
    }  
}

Product.cs

/// <summary>
/// Observations -  
///     1. Products - (Two) MixtureProduct
/// What's new here -
///     1. Product - One additional type of product is added
/// </summary>
namespace FactoryWithMultipleResponsibility
{
    /// <summary>
    ///  Its an essential function of blender, no matter what kind of product its going to mix
    /// </summary>
    public interface IMixedProduct
    {
        void BlendProduct(string readyProduct);
    }
    /// <summary>
    /// Concrete product implementing essential method
    /// </summary>
    public class MixtureProduct : IMixedProduct
    {
        public void BlendProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is mixed.");
        }
    }
 
    /// <summary>
    /// A class for additional function of multiple responsibility
    /// </summary>
    public class MashedProduct
    {
        public void MashProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is mixed.");
        }
    }
  
}

第四阶段存在的问题

首先,它的输出不尽如人意,因为我是用手捣碎土豆的;其次,它无法满足制作果汁或研磨蔬菜等需求。

用编程术语来说

有时,我们试图将同一个类用于它并非为之设计的职责,仅仅因为它有一些相似的方法和属性,并且我们想节省时间和精力。

注意点

违反 SOLID SRP 规则 - 将多种类型的职责分配给一个类。一个类应该只有一个改变的原因。

需求分析

我兴致勃勃地探索厨房里通常还需要什么,以及我能用这个搅拌机做什么?因此,通常,我还需要研磨蔬菜来制作冰沙或研磨谷物来制作面团,所以为什么不拥有另一个设备呢?

第五阶段 - 使用多个工厂 - 可行性较低的解决方案

解决方案(在现实世界场景中)

我决定为所有其他物品,如固体或半固体物品,购买一个单独的搅拌机,这样我就可以获得预期的其他物品的输出。

用编程术语来说

我决定将不同的职责委托给每个产品类型的独立工厂,以避免违反 SRP。

类图

实现代码

UIClass.cs

using System;

/// <summary>
/// Observations -
///     1. Object instantiation of specific factories depends on input type 
/// What's new here -
///     1. UI - Main class calling specific concrete factories according to input type
/// Problem -
///     1. UI class with multiple type of responsibility
///     2. UI code will be changed, if additional responsibility comes in scope
///     3. Maintaining consistency among factories becomes complicated here
/// </summary>
namespace MultipleFactories
{
    /// <summary>
    /// UI code calling specific factories as per input type
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            // Code added for mixing and grinding items
            Console.WriteLine("Please enter item for Mixer : Fruit, Boiled, or Grain");           
            string itemType = Console.ReadLine();
 
            switch (itemType)
            {
                case "Fruit":
                    JuiceFactory juice = new JuiceFactory();
                    juice.GetJuiceProduct(itemType);
                    break;
                case "Boiled":
                    MashingFactory semiLiquid = new MashingFactory();
                    semiLiquid.GetMashProduct(itemType);
                    break;
                case "Grain":
                    GrindingFactory flour = new GrindingFactory();
                    flour.GetGrindedProduct(itemType);
                    break;
            }
 
            Console.ReadKey();
        }
    }
}

Factory.cs

/// <summary>
/// What's New -
///     1.  There are separate factory for all type like juice, boiled, flour
/// Problem -
///     1. There is repetition of code involve in each factory, 
///     and if it require changes, changes need everywhere
/// Object instantiation of product
/// </summary>
namespace MultipleFactories
{
    /// <summary>
    /// Concrete juice factory calling required methods
    /// </summary>
    public class JuiceFactory
    {
        public JuiceProduct GetJuiceProduct(string inputType)
        {
            JuiceProduct juice = new JuiceProduct();
            string readyProduct = juice.sqeezeProduct();
 
            IMixedProduct endProduct = new MixtureProduct();
            endProduct.MixProduct(readyProduct);
            return juice;
        }
    }
 
    /// <summary>
    /// Concrete mashing factory calling required methods
    /// </summary>
    public class MashingFactory
    {
        public MashedProduct GetMashProduct(string inputType)
        {
            MashedProduct mash = new MashedProduct();
            string readyProduct = mash.mashProduct();
 
            new MixtureProduct().MixProduct(readyProduct);
            return mash;
        }
    }
 
    /// <summary>
    /// Concrete grinding factory calling required methods
    /// </summary>
    public class GrindingFactory
    {
        public FlourProduct GetGrindedProduct(string inputType)
        {
            FlourProduct flour = new FlourProduct();
            string readyProduct = flour.grindProduct();
 
            new MixtureProduct().MixProduct(readyProduct);
            return flour;
        }
    }
}

Product.cs

using System;

/// <summary>
/// Observations -  
///     1. Products - One child product inheriting abstract product and three separate products
/// What's new here -
///     1. Product - One additional type of product is added
/// </summary>
namespace MultipleFactories
{
    /// <summary>
    ///  Its an essential function of blender, no matter what kind of product its going to mix
    /// </summary>
    public interface IMixedProduct
    {
        void MixProduct(string readyProduct);
    }
 
    /// <summary>
    /// Concrete product implementing essential method
    /// </summary>
    public class MixtureProduct : IMixedProduct
    {
        public  void MixProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " is mixed.");
        }
    }
 
    /// <summary>
    /// Squeezing function for juices only
    /// </summary>
    public class JuiceProduct
    {
        public string sqeezeProduct()
        {
            return "Juice";
        }
    }
 
    /// <summary>
    /// Mashing function for boiled item only
    /// </summary>
    public class MashedProduct
    {
        public string mashProduct()
        {
            return "Mashed product";
        }
    }
 
    /// <summary>
    /// Grinding function for grain items only
    /// </summary>
    public class FlourProduct
    {
        public string grindProduct()
        {
            return "Flour";
        }
    }
}

第五阶段存在的问题

我以为我已经完成了厨房电器的配置,但事情并未就此结束,过了一段时间,我需要研磨小麦来制作面团。我既不能使用搅拌机或研磨机来研磨小麦,也不想在我的厨房里购买(即额外的努力)另一个电器(即管理代码)。

用编程术语来说 / 注意点 / 需求分析

  • 我想获得所有液体、半液体、固体物品(如水果、蔬菜、小麦、果泥、番茄、土豆等)的所有研磨功能。
  • 我不想牺牲特定的结果或质量。
  • 我不想购买很多电器,因为这既不经济,也不易于管理。

第六阶段 - 工厂方法模式

(也称为工厂方法、工厂方法模式、工厂方法设计模式)

解决方案

因此,我没有购买许多电器,而是购买了一个带有独立罐子的组合式JuicerMixerGrinder(榨汁机/搅拌机/研磨机)。

现实世界场景

当您需要水果汁时,您会寻找榨汁机,这就像一个小工厂(搅拌机 - 创建者,罐子 - 具体创建者,鸡蛋/水果 - 原材料,果泥/果汁 - 具体产品)。因此,根据您放入搅拌机中的物品(输入),它可以产生果泥、奶昔或果汁。因此,当搅拌机搅拌或研磨物品时,它不知道它将要压碎哪个物品(即,让子类决定要实例化哪个类)。

定义(用编程术语来说)

“工厂方法模式定义了一个用于创建对象的接口,但让子类决定实例化哪个类。工厂方法允许类将实例化推迟到子类。”

将设计模式术语与现实世界示例进行比较

  • 您 - 主方法
  • 搅拌机 - 工厂或创建者
  • 罐子 - 具体创建者
  • 物品 - 原材料

解决方案(用编程术语来说)

这里有两种方法

方法 1

可能存在多个具体工厂,它们实现特定的方法,具体取决于输入类型和工厂接口中定义的必要(所有产品共有的方法,即混合)方法来实例化最终产品。

方法 2

可能存在一个具体的工厂,它根据输入类型实现产品特定的方法,然后工厂接口中定义了必要(所有产品共有的方法,即混合)方法来实例化最终产品。

第六阶段(方法一)

类图

实现代码

UIClass.cs

using System;
/// <summary>
/// Observations -
///     1. Object instantiation of specific factories depends on input type 
///     2. Caller of factory(UI Class in this example) knows which factory to call, 
///        but don't know which end product to prepare
/// What's new here -
///     1. This is one of the approaches for Factory method pattern, 
///        where UI class initialize specific concreate factory
/// </summary>
using System;
namespace FactoryMethod_App1
{
    /// <summary>
    /// UI code calling specific factories as per input type
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            // Code added for mixing and grinding items
            Console.WriteLine("Please enter item for Mixer : Fruit, Boiled, or Grain");           
            string itemType = Console.ReadLine();
 
            switch (itemType)
            {
                case "Fruit":
                    // Instantiating concreate factory
                    IMixerFactory juice = new JuiceFactory();
                    //Creation of product using method in factory (that is factory method)
                    IMixer mixJuice = juice.GetMixingProduct();
                    // Processing end product
                    mixJuice.MixProduct(itemType);
                    // above 2 lines of code can also be written in single line 
                    //juice.GetMixingProduct().MixProduct(itemType);
                    break;
                case "Boiled":
                   IMixerFactory boiled = new MashingFactory();
                    IMixer mash = boiled.GetMixingProduct();
                    mash.MixProduct(itemType);
                    break;
                case "Grain":
                   IMixerFactory crush = new GrindingFactory();
                    IMixer flour = crush.GetMixingProduct();
                    flour.MixProduct(itemType);
                    break;
                default :
                    throw new Exception("item type not supported");
            }
            Console.ReadKey();
        }
    }
}

Factory.cs

/// <summary>
/// Observations - 
///     1. There are seperate concreate factories for all type like juice, boiled, flour
///     2. Here mixer has 3 separate jars for juicer, masher, & grinder
/// What's New -
///     1. Introduceds single factory interface to be implemented by all concreate factories
/// </summary>
namespace FactoryMethod_App1
{
    /// <summary>
    /// Observations - 
    ///     Single factory interface to be implemented by all products  
    /// </summary>
    public interface IMixerFactory
    {
        IMixer GetMixingProduct();
    } 
    /// <summary>
    ///      Concreate juice factory calling required methods
    /// </summary>
    public class JuiceFactory : IMixerFactory
    {
        public IMixer GetMixingProduct()
        {
            return new Juicer();
        }
    }
 
    /// <summary>
    /// Concrete mashing factory calling required methods
    /// </summary>
    public class MashingFactory : IMixerFactory
    {
        public IMixer GetMixingProduct()
        {
            return new Masher();
        }
    }
 
    /// <summary>
    /// Concrete grinding factory calling required methods
    /// </summary>
   public class GrindingFactory : IMixerFactory
    {
        public IMixer GetMixingProduct()
        {
            return new Grinder();
        }
    }
}

Product.cs

using System;
/// <summary>
/// Observations - 
///     1. Each function is implementing interface method to process end product 
/// </summary>
namespace FactoryMethod_App1
{
    /// <summary>
    ///  Product interface
    /// </summary>
    public interface IMixer
    {
        void MixProduct(string readyProduct);
    }
 
    /// <summary>
    /// Juicer implements interface method
    /// </summary>
    public class Juicer : IMixer
    {
        public void MixProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " juice is prepared.");
        }
    }
 
    /// <summary>
    /// Masher implements interface method
    /// </summary>
    public class Masher : IMixer
    {
        public void MixProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " product is mashed.");
        }
    }
 
    /// <summary>
    /// Grinder implements interface method
    /// </summary>
    public class Grinder : IMixer
    {
        public void MixProduct(string readyProduct)
        {
            Console.WriteLine(readyProduct + " flour is prepared.");
        }
    }
}

第六阶段(方法二)

类图

实现代码

UIClass.cs

using System;
/// <summary>
/// Observations -
///     1. This is another approach for Factory method pattern, 
///        where UI class initialize single concreate factory
///     2. Object instantiation of main concreate factories depends on input type using interface
/// </summary>
using System;
namespace FactoryMethod_App2
{
    /// <summary>
    /// UI code calling specific factories as per input type
    /// </summary>
    class UIClass
    {
        static void Main(string[] args)
        {
            // Code added for mixing and grinding items
            Console.WriteLine("Please enter item for Mixer : Fruit, Boiled, or Grain");           
           
             // Note - Using this method, UI code will not be changed.
            IMixerFactory liquid = new MixerFactory();
            IMixer endproduct = liquid.GetMixingProduct(Console.ReadLine());
                        
            Console.ReadKey();
        }
    }
}

Factory.cs

/// <summary>
/// Observations - 
///    1. There is only one factory for all type like juice, boiled, flour
///    2. Here mixer has 3 function for juicer, masher, & grinder
/// What's New -
///    1. Single factory interface to be implemented by single concreate factory
/// </summary>
namespace FactoryMethod_App2
{
    /// <summary>
    /// Observations - 
    ///     Single factory interface to be implemented by all products
    ///     Abstract class can also be used here instead of interface 
    /// </summary>
    public interface IMixerFactory
    {
        IMixer GetMixingProduct(string inputType);
    }
    /// <summary>
    ///     Single concreate factory instantiating concreate products
    /// </summary>
    public class MixerFactory : IMixerFactory
    {
        public IMixer GetMixingProduct(string inputType)
        {
            return new Mixer(inputType);
        }
    }
}

Product.cs

using System;
/// <summary>
/// Observations - 
///     1. Here Mixer implements interface method after making product ready to mix
///     2. Mixer using switch case to get specific product ready before mixing
/// </summary>
namespace FactoryMethod_App2
{
    /// <summary>
    ///  Product interface
    /// </summary>
    public interface IMixer
    {
        string GetProductReady(string readyProduct);
    }
 
    /// <summary>
    ///  Common mixing function for all item types
    /// </summary>
  public class Mixer : IMixer
    {
        public Mixer(string inputType)
        {
            string endproduct = GetProductReady(inputType);
            Console.WriteLine(inputType + " " + endproduct + " is prepared.");
        }
        // Getting product ready before mixing
        public string GetProductReady(string inputType)
        {
            string readyProduct = null;
            switch (inputType)
            {
                case "Fruit":
                    readyProduct = new Juicer().sqeezeProduct();
                    break;
                case "Boiled":
                   readyProduct = new Masher().mashProduct();
                    break;
                case "Grain":
                    readyProduct = new Grinder().grindProduct();
                    break;
                default :
                    throw new Exception("item type not supported");
            }
            return readyProduct;
        }
    }
    /// <summary>
    /// Squeezing function for fruit item 
    /// </summary>
    public class Juicer 
    {
        public string sqeezeProduct()
        {
            return "juice";
        }
    }
 
    /// <summary>
    /// Mashing function for boiled item only
    /// </summary>
   public class Masher 
    {
        public string mashProduct()
        {
            return "mashed product";
        }
    }
     /// <summary>
    /// Grinding function for solid item only
    /// </summary>
   public class Grinder
    {
        public string grindProduct()
        {
            return "flour";
        }
    }
}

常见问题解答

  1. 工厂模式是否总是需要使用 switch 语句?

    不,这并非必需,这取决于如何管理具体工厂的实例化。

  2. 根据定义,工厂方法可以仅在接口中定义吗,它定义了一个接口?

    不,也可以使用abstract方法。

  3. 由于此示例使用Abstract关键字来定义产品,所以它是否类似于abstract工厂模式?

    不,使用Abstract这个词是为了唯一地定义一个具有必要方法的产品,abstract工厂模式具有不同层级的结构。

  4. 在此示例中,具体工厂方法使用方法组合,那么它是否类似于abstract工厂方法?

    这里使用了组合,只是为了从 UI 中实例化单个方法,在抽象工厂中,Abstract工厂的定义内部使用了abstract关键字与abstract方法的组合。更多详情,请参阅另一篇关于abstract工厂的文章。

参考文献

  1. https://www.dofactory.com/net/factory-method-design-pattern

    Factory

  2. https://garywoodfine.com/
  3. https://dev.to/gary_woodfine/simple-factory-pattern-in-c-and-net-core-3263
  4. https://www.oodesign.com/factory-pattern.html
  5. https://www.c-sharpcorner.com/UploadFile/akkiraju/factory-design-pattern-vs-factory-method-design-pattern/
  6. http://shop.oreilly.com/product/9780596007126.do
  7. https://www.c-sharpcorner.com/UploadFile/akkiraju/factory-design-pattern-vs-factory-method-design-pattern/
  8. https://vivekcek.wordpress.com/2013/03/17/simple-factory-vs-factory-method-vs-abstract-factory-by-example/

关注点

互联网上有很多插图,请仔细阅读几个网站,以了解实际使用它的地方。

© . All rights reserved.