工厂设计模式(需要评论)

时间:2010-11-04 18:31:24

标签: c# design-patterns factory-pattern

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

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

工厂模式是什么? 工厂模式利用特定的专用“对象创建器对象”来处理 - 对象的创建 - 并且大部分时间 - 实例化,类似于现实世界的工厂。

真实世界的例子
想象一下汽车工厂是各种类型汽车的创造者。那个汽车厂的装配线之一可能有一天会生产一辆卡车,但是在另一天可能会重新生产汽车。假设经销商向其指定的帐户处理部门下达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

3 个答案:

答案 0 :(得分:14)

对不起。那是一个非常不灵活的工厂。反射可以giva一些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类)自己注册。这要求工厂遵循单件模式。

这里有救援的反思。 Reflection允许您遍历DLL和EXE中的所有类型。因此,我们可以搜索实现我们接口的所有类,因此能够为所有类构建字典。

答案 1 :(得分:3)

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

一些可能的变化:

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

答案 2 :(得分:0)

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