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

发布于 2024-10-01 08:53:03 字数 4412 浏览 2 评论 0原文

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

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

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

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

I am putting together an explanation and code example of this design pattern, attempting to help others around me grasp it (along with helping myself to master the pattern as well).

What I am looking for is opinions & or critique of my explanation and code sample...thanks!

What is the factory pattern?
The factory pattern utilizes a particular dedicated "object creator object" to handle creation of - and most times instantiation of - objects, similar to a real world factory.

Real world example
Think of an automobile factory being the creator of various types of automobiles. One of the assembly lines in that automobile factory may produce a truck one day, but on another day may be re-tooled for producing cars. Say a dealership places an order for 10 cars to their assigned account handling department. That department then utilizes a certain factory and orders up the 10 cars. The account handlers are not concerned with making the cars themselves (imagine the poor results) they only work with the final product, ensuring the dealership gets their vehicles.

A new model of that same car comes out the next year and orders begin flowing in. The account handlers (still not concerned with the production of the car) place the orders, but now the car they receive is different, the assembling method or even maybe the factory altogether may be different, yet the account handlers need not worry about this. An additional thought: the factory assemblers of the vehicles may know exactly what action to take if a certain account handler places an order (i.e. account handler X places an order, factory assembler knows that for account handler X, they produce 10 vehicles of type Y). Another option may be that the account handler tells the assembler exactly what type of vehicle to produce.

If the account handlers also handled the creation of the vehicles (i.e. they were coupled), every time a vehicle changed in any way, each of the account handlers would have to be retrained in producing that vehicle. This would create quality issues as there are far more account handlers than there would be factories...mistakes would happen, expense would be far greater.

Circling back to OOP
An object factory as a design pattern applied to software engineering is similar to the above example in concept… The factory churns out various types of other objects, you can utilize an assembly line (object assembler) which produces a certain object type, returned in a certain way. The assembler can either inspect the requesting client and handle, or the client may tell the assembler what object it requires. Now...you are on a project and create an object factory and various assemblers, later on down the road in the project, the requirements change slightly, you are now asked to alter object contents and how its clients are handling that object. Since you utilized the factory pattern this is a simple change and in one location, you can change or add the objects the factory produces, and alter the format in which the assemblers lay the object contents out.

The unfortunate way to have done this would have been without a factory method, instantiating each object instance and formatting object contents in the clients themselves...say you used this particular object in 20 clients. Now you must go to each one of the clients, alter each of the object instances and formats...what a waste of time…Be lazy...do it the right way the first time so you save yourself (and others) time and effort later.

Code example (C#)
Below is an example utilizing a factory for food and various food objects

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

听你说爱我 2024-10-08 08:53:03

对不起。那是一家相当僵化的工厂。反思可以带来一些 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 语句(但如果需要枚举,这是最好的方法)。最好能够以某种方式注册所有不同的类。通过配置文件或允许实际实现(例如汉堡类)自行注册。这就要求工厂遵循单例模式。

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

Sorry. That is a quite inflexxible factory. Reflection can giva some 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);
    }

}

Usage:

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

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

Edit, feedback on your code:

First of all, factories are used to be able to create objects with a little code changes as possible when adding new types of implementations. Using an enum means that all places that are invoking the factory need to use an enum and be updated when the enum changes.

Sure, it's still a bit better than creating types directly.

The second problem with your code is that you are using a switch statement (but that's the best way to do it if the enum is an requirement). It's better to be able to register all different classes in some way. Either from a config file or by allowing the actual implementations (for instance the Hamburger class) to register themselves. This requires that the factory follows the singleton pattern.

Here comes Reflection to the rescue. Reflection allows you to go through all types in DLLs and EXEs. So we can search for all classes that implements our interface and therefore be able to build a dictionary will all classes.

再浓的妆也掩不了殇 2024-10-08 08:53:03

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

一些可能的更改:

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

I think your explanation including the real world example is good. However, I don't think your example code shows the real benefits of the pattern.

Some possible changes:

  • I wouldn't have the enum in parallel to the types. This looks like you have to update the enum every time a type is added. It might be more appropriate to pass the System.Type. Then you can even make the factory a generic with a template argument.
  • I think the pattern is more "impressive" if you use it for creating something like a hardware interface. You'd then have a "AbstractNetworkDevice" and all your callers don't know which hardware setup you have. But the factory can create a "TcpNetworkDevice" or a "SerialNetworkDevice" or whatever based on some configuration which was made at startup.
走野 2024-10-08 08:53:03

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

I would suggest you use interfaces instead of abstract classes/inheritance. Other than that, it looks OK.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文