【问题标题】:Factory Design Pattern (needing critique)工厂设计模式(需要批评)
【发布时间】:2013-11-01 14:20:56
【问题描述】:

我正在整理这个设计模式的解释和代码示例,试图帮助我周围的其他人掌握它(同时帮助我自己掌握这个模式)。

我正在寻找的是对我的解释和代码示例的意见或批评...谢谢!

什么是工厂模式? 工厂模式利用一个特定的专用“对象创建者对象”来处理对象的创建——以及大多数情况下的实例化——对象,类似于现实世界的工厂。

现实世界的例子
想象一家汽车厂是各种汽车的创造者。该汽车厂的一条装配线可能会在某一天生产一辆卡车,但在另一天可能会重新装配以生产汽车。假设经销商向其指定的客户处理部门订购了 10 辆汽车。然后,该部门利用某家工厂订购了 10 辆汽车。客户处理人员并不关心自己制造汽车(想象一下糟糕的结果),他们只使用最终产品,确保经销商得到他们的车辆。

同一辆车的新车型第二年问世,订单开始涌入。客户处理人员(仍然不关心汽车的生产)下订单,但现在他们收到的汽车不同了,组装方法甚至工厂可能完全不同,但帐户处理程序不必担心这一点。一个额外的想法:车辆的工厂组装商可能确切地知道如果某个账户处理人下订单(即账户处理人 X 下订单,工厂组装商知道对于账户处理人 X,他们生产 10 辆 Y 型车辆)要采取什么行动)。另一种选择可能是帐户处理程序准确地告诉组装商要生产哪种类型的车辆。

如果帐户处理程序还处理车辆的创建(即它们是耦合的),则每次车辆以任何方式发生变化时,每个帐户处理程序都必须重新培训以生产该车辆。这将产生质量问题,因为帐户处理人员的数量远远多于工厂数量......会发生错误,费用会高得多。

回到 OOP
对象工厂作为一种应用于软件工程的设计模式,在概念上类似于上面的示例……工厂生产出各种类型的其他对象,您可以利用生产某种对象类型的装配线(对象装配器),以某种方式。组装器可以检查请求的客户端并处理,或者客户端可以告诉组装器它需要什么对象。现在......您在一个项目中并创建了一个对象工厂和各种组装器,稍后在项目的道路上,需求略有变化,您现在被要求更改对象内容以及其客户如何处理该对象。由于您使用了工厂模式,这是一个简单的更改,并且在一个位置,您可以更改或添加工厂生成的对象,并更改汇编器放置对象内容的格式。

不幸的做法是没有工厂方法,实例化每个对象实例并在客户端本身中格式化对象内容......假设您在 20 个客户端中使用了这个特定对象。现在你必须去找每一个客户,改变每一个对象实例和格式......真是浪费时间......懒惰......第一次做正确的方式,这样你就可以节省自己(和其他人)的时间以后再努力。

代码示例 (C#)
下面是一个利用工厂生产食品和各种食品的例子

Factory module
    public enum FoodType
    {
    //enumerated foodtype value, if client wants to specify type of object, coupling still occurs
        Hamburger, Pizza, HotDog
    }
 
    /// <summary>
    /// Object to be overridden (logical)
    /// </summary>
    public abstract class Food
    {
        public abstract double FoodPrice { get; }
    }
 
    /// <summary>
    /// Factory object to be overridden (logical)
    /// </summary>
    public abstract class FoodFactory
    {
        public abstract Food CreateFood(FoodType type);
    }
 
    //-------------------------------------------------------------------------
    #region various food objects
    class Hamburger : Food
    {
        double _foodPrice = 3.59;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
 
    class Pizza : Food
    {
        double _foodPrice = 2.49;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
 
    class HotDog : Food
    {
        double _foodPrice = 1.49;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
    #endregion
    //--------------------------------------------------------------------------
 
 
    /// <summary>
    /// Physical factory
    /// </summary>
    public class ConcreteFoodFactory : FoodFactory
    {
        public override Food CreateFood(FoodType foodType)
        {
            switch (foodType)
            {
                case FoodType.Hamburger:
                    return new Hamburger();
                    break;
                case FoodType.HotDog:
                    return new HotDog();
                    break;
                case FoodType.Pizza:
                    return new Pizza();
                    break;
                default:
                    return null;
                    break;
            }
        }
    }
 
    /// <summary>
    /// Assemblers
    /// </summary>
    public class FoodAssembler
    {
        public string AssembleFoodAsString(object sender, FoodFactory factory)
        {
            Food food = factory.CreateFood(FoodType.Hamburger);
            if (sender.GetType().Name == "default_aspx")
            {
                return string.Format("The price for the hamburger is: ${0}", food.FoodPrice.ToString());
            }
            else
            {
                return food.FoodPrice.ToString();
            }  
        }
 
        public Food AssembleFoodObject(FoodFactory factory)
        {
            Food food = factory.CreateFood(FoodType.Hamburger);
            return food;
        }
    }

Calling factory
FoodFactory factory = new ConcreteFoodFactory(); //create an instance of the factoryenter code here
lblUser.Text = new FoodAssembler().AssembleFoodAsString(this, factory); //call the assembler which formats for string output

Object o = new FoodAssembler().AssembleFoodObject(factory); //example: instantiating anon object, initialized with created food object

【问题讨论】:

  • 也许是社区维基?
  • 为什么票数接近?这个问题是有效的,虽然它有点长:)

标签: c# design-patterns factory-pattern


【解决方案1】:

对不起。那是一个相当不灵活的工厂。反射可以给出一些 POWWAH!!

public interface IFood
{
    bool IsTasty { get; }
}
public class Hamburger : IFood
{
    public bool IsTasty {get{ return true;}}
}
public class PeaSoup : IFood
{
    public bool IsTasty { get { return false; } }
}

public class FoodFactory
{
    private Dictionary<string, Type> _foundFoodTypes =
        new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Scan all specified assemblies after food.
    /// </summary>
    public void ScanForFood(params Assembly[] assemblies)
    {
        var foodType = typeof (IFood);
        foreach (var assembly in assemblies)
        {
            foreach (var type in assembly.GetTypes())
            {
                if (!foodType.IsAssignableFrom(type) || type.IsAbstract || type.IsInterface)
                    continue;
                _foundFoodTypes.Add(type.Name, type);
            }
        }

    }

    /// <summary>
    /// Create some food!
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public IFood Create(string name)
    {
        Type type;
        if (!_foundFoodTypes.TryGetValue(name, out type))
            throw new ArgumentException("Failed to find food named '" + name + "'.");

        return (IFood)Activator.CreateInstance(type);
    }

}

用法:

var factory = new FoodFactory();
factory.ScanForFood(Assembly.GetExecutingAssembly());

Console.WriteLine("Is a hamburger tasty? " + factory.Create("Hamburger").IsTasty);

编辑、反馈您的代码:

首先,工厂习惯于在添加新类型的实现时能够通过尽可能少的代码更改来创建对象。使用枚举意味着所有调用工厂的地方都需要使用枚举并在枚举更改时更新。

当然,还是比直接创建类型好一点。

您的代码的第二个问题是您使用的是 switch 语句(但如果需要枚举,这是最好的方法)。最好能够以某种方式注册所有不同的类。来自配置文件或允许实际实现(例如 Hamburger 类)注册自己。这就要求工厂遵循单例模式。

反射来拯救。反射允许您遍历 DLL 和 EXE 中的所有类型。所以我们可以搜索所有实现我们接口的类,从而能够为所有类构建一个字典。

【讨论】:

  • 这是我希望得到的反馈类型,你能评论一下我的例子,向以前从未听说过这种模式的人解释它吗?我不确定我的例子是否准确且有意义。感谢您的输入,标记为答案。
  • 当你可以使用泛型时为什么要使用反射,除非你想从文件中读取食物类型或类似的东西,使用字符串作为类名只会让它更容易出错。
  • 说“汉堡包”并没有以任何方式决定实现的样子。说 factory.Get() 会强制您的所有实现都从 Hamburger 派生。字符串替代的另一个好处是代码可以使用它不知道的实现(例如来自动态加载的程序集)。
  • OK... 看到人们以某种​​方式认为不正确的代码不需要编辑。任何希望使用此代码的人都应注意“if (!foodType.IsAssignableFrom(type) || foodType.IsAbstract || foodType.IsInterface)”行应为“if (type.IsAbstract || type.IsInterface || !foodType.IsAssignableFrom(type))"
  • 为什么需要编辑?你所做的只是交换支票的位置。评价结果还是一样。还是你在微优化?
【解决方案2】:

我认为您的解释(包括现实世界的示例)很好。但是,我不认为您的示例代码显示了该模式的真正好处。

一些可能的变化:

  • 我不会让枚举与类型并行。这看起来您必须在每次添加类型时更新枚举。传递 System.Type 可能更合适。然后,您甚至可以使用模板参数将工厂设为泛型。
  • 我认为如果你用它来创建类似硬件接口的东西,这种模式会更“令人印象深刻”。然后,您将拥有一个“AbstractNetworkDevice”,而您的所有呼叫者都不知道您拥有哪种硬件设置。但是工厂可以创建一个“TcpNetworkDevice”或一个“SerialNetworkDevice”或任何基于启动时进行的配置。

【讨论】:

  • 感谢 phillipp 的建议,我会赞成这个,但还是太新了
【解决方案3】:

我建议您使用接口而不是抽象类/继承。除此之外,它看起来还可以。

【讨论】:

  • 不。不能使用 switch 语句和枚举
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-20
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多