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

6
我正在编写一个设计模式的解释和代码示例,试图帮助身边的人掌握它(同时也帮助自己掌握该模式)。
我想要的是对我的解释和代码示例的意见或批评...谢谢!
工厂模式是什么? 工厂模式利用特定的“对象创建对象”来处理对象的创建和实例化,类似于现实世界中的工厂。
现实世界的例子 想象一下汽车工厂是各种类型汽车的制造商。汽车工厂的其中一条生产线可能在某一天生产卡车,但在另一天可能会重新配置为生产汽车。假设一家经销商向其指定的客户处理部门订购了10辆汽车。然后该部门使用特定的工厂并订购了这10辆汽车。客户处理人员不关心制作汽车本身(想象一下可怜的结果),他们只处理最终产品,确保经销商获得他们的车辆。
接下来的一年里,同样的车型推出了新款,并开始大量订购。客户处理人员(仍然不关心汽车的生产)下订单,但现在他们收到的汽车是不同的,组装方法甚至可能完全不同,但客户处理人员不需要担心这一点。另一个想法是,汽车制造商可能知道如果某个客户处理人员下订单时应该采取什么行动(例如,客户处理人员X下订单,工厂组装者知道他们为客户处理人员X生产10辆Y型车)。另一种选择可能是客户处理人员告诉组装者要生产什么类型的车辆。
如果客户处理人员也负责车辆的创建(即它们被耦合在一起),每当车辆以任何方式更改时,每个客户处理人员都必须接受重新培训以生产该车辆。这将导致质量问题,因为客户处理人员比工厂数量多得多...错误将会发生,费用将会大大增加。
回到面向对象编程 将对象工厂作为应用于软件工程的设计模式与上述概念类似...工厂生产各种其他对象,您可以使用组装线(对象组装器)生产特定类型的对象,并以特定方式返回。组装器可以检查请求的客户端并处理,或者客户端可能会告诉组装器它需要哪个对象。现在...您正在一个项目中创建一个对象工厂和各种组装器,项目在未来的某个时间里需求稍有变化,您现在被要求更改对象内容以及客户端如何处理该对象。由于您使用了工厂模式,这是一个简单的更改,并且可以在一个位置更改或添加工厂生产的对象,并更改组装器将对象内容布局的格式。
使用工厂方法的不幸方式是在客户端中实例化每个对象实例并格式化对象内容……假设您在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

社区维基,也许? - Etienne de Martel
为什么有关闭投票?虽然问题有点长,但是它是有效的。 - jgauffin
3个回答

14

抱歉,那是一个非常死板的工厂。反射可以提供一些能量!!

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语句(但如果枚举是一个必需品,则这是最好的方法)。更好的做法是能够以某种方式注册所有不同的类。可以通过配置文件或允许实际实现(例如汉堡包类)来自行注册。这要求工厂遵循单例模式。

这里反射拯救了我们。反射使您可以浏览DLL和EXE中的所有类型。因此,我们可以搜索所有实现我们接口的类,并因此能够构建包含所有类的字典。


这是我所期望的反馈类型,您能否评论一下我的示例,以便向从未听说过该模式的人解释?我想确保我的示例准确且有意义。感谢您的回复,我会将其标记为答案。 - dbobrowski
为什么要使用反射而不是泛型呢?除非你想从文件或类似的地方读取食品类型,否则将类名表示为字符串只会增加错误的可能性。 - Doggett
说“汉堡包”并不以任何方式指示实现的样子。说factory.Get<Hamburger>()会迫使所有的实现派生自Hamburger。另一个使用字符串替代方案的好处是代码可以使用它没有任何了解的实现(例如来自动态加载的程序集)。 - jgauffin
好的...由于人们认为不正确的代码不值得编辑,因此任何希望使用此代码的人都应注意,行"if (!foodType.IsAssignableFrom(type) || foodType.IsAbstract || foodType.IsInterface)"应改为"if (type.IsAbstract || type.IsInterface || !foodType.IsAssignableFrom(type))"。 - TheManWithNoName
3
为什么需要进行这次编辑?你所做的只是调换了检查的位置,评估结果还是一样的。难道你在微观优化吗? - jgauffin
这个例子可以很容易地通过将其变成“通用工厂”来改进。我在这里演示了这一点 - RobIII

3
我认为你的解释和实际世界示例很好。然而,我不认为你的示例代码展示了该模式的真正优点。
以下是一些可能的更改:
- 我不会将枚举与类型并列。这看起来像是每次添加类型都要更新枚举。更合适的做法可能是传递System.Type。然后,您甚至可以使工厂成为带有模板参数的泛型。 - 如果您将其用于创建硬件接口之类的内容,则该模式更具“印象力”。那么您将拥有一个“AbstractNetworkDevice”,所有调用者都不知道您拥有哪种硬件设置。但是根据在启动时进行的某些配置,工厂可以创建“TcpNetworkDevice”或“SerialNetworkDevice”等设备。

谢谢你的建议Phillipp,我会点赞这个,但是我还太新了。 - dbobrowski

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

2
不要这样做。使用switch语句和枚举是不好的。 - jgauffin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接