我的工厂方法是否做得太过分了?

发布于 2024-08-15 03:25:24 字数 751 浏览 3 评论 0原文

我们核心产品的一部分是使用各种页面小部件的网站 CMS。这些小部件负责显示内容、列出产品、处理事件注册等。每个小部件都由派生自基本小部件类的类表示。渲染页面时,服务器从数据库中获取页面的小部件,然后创建正确类的实例。工厂方法对吗?

Private Function WidgetFactory(typeId)
    Dim oWidget
    Select Case typeId
        Case widgetType.ContentBlock
            Set oWidget = New ContentWidget
        Case widgetType.Registration
            Set oWidget = New RegistrationWidget
        Case widgetType.DocumentList
            Set oWidget = New DocumentListWidget
        Case widgetType.DocumentDisplay
    End Select
    Set WidgetFactory = oWidget
End Function

无论如何,这一切都很好,但随着时间的推移,小部件类型的数量已增加到 50 种左右,这意味着工厂方法相当长。每次我创建一种新类型的小部件时,我都会在该方法中添加另外几行,并且我的脑海中会响起一个小警报,这可能不是最好的方法。我倾向于忽略那个警报,但它越来越响。

那么,我做错了吗?有没有更好的方法来处理这种情况?

Part of our core product is a website CMS which makes use of various page widgets. These widgets are responsible for displaying content, listing products, handling event registration, etc. Each widget is represented by class which derives from the base widget class. When rendering a page the server grabs the page's widget from the database and then creates an instance of the correct class. The factory method right?

Private Function WidgetFactory(typeId)
    Dim oWidget
    Select Case typeId
        Case widgetType.ContentBlock
            Set oWidget = New ContentWidget
        Case widgetType.Registration
            Set oWidget = New RegistrationWidget
        Case widgetType.DocumentList
            Set oWidget = New DocumentListWidget
        Case widgetType.DocumentDisplay
    End Select
    Set WidgetFactory = oWidget
End Function

Anyways, this is all fine but as time has gone on the number of types of widgets has increased to around 50 meaning the factory method is rather long. Every time I create a new type of widget I go to add another couple of lines to the method and a little alarm rings in my head that maybe this isn't the best way to do things. I tend to just ignore that alarm but it's getting louder.

So, am I doing it wrong? Is there a better way to handle this scenario?

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

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

发布评论

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

评论(7

醉生梦死 2024-08-22 03:25:24

我认为您应该问自己的问题是:为什么我在这里使用工厂方法?

如果答案是“因为 A”,并且A 是一个很好的理由,然后继续这样做,即使这意味着一些额外的代码。如果答案是“我不知道;因为我听说你应该这样做?”那么你应该重新考虑。

让我们回顾一下使用工厂的标准原因。以下是维基百科对工厂方法模式的描述

[...],它处理创建对象(产品)的问题,而不指定将创建的对象的确切类。工厂方法设计模式通过定义一个单独的方法来创建对象来处理这个问题,然后可以重写该对象的子类来指定将要创建的产品的派生类型。

由于您的 WidgetFactoryPrivate,这显然不是您使用此模式的原因。 “工厂模式”本身怎么样(无论您是使用工厂方法还是抽象类实现它)?再次,维基百科说

在以下情况下使用工厂模式:

  • 对象的创建会在没有大量重复代码的情况下阻止重用。
  • 创建对象需要访问不适合包含在组合对象中的信息或资源。
  • 需要集中管理已创建对象的生命周期,以确保行为一致。

从您的示例代码来看,这一切都不符合您的需求。因此,问题(只有您可以回答)是:(1) 您将来的小部件需要集中式工厂功能的可能性有多大,以及 (2) 成本有多大如果您将来需要它,是否可以将所有内容更改回工厂方法?如果两者都较低,您可以暂时安全地放弃 Factory 方法。


编辑:在一般性阐述之后,让我回到您的特殊情况:通常,它是 a = new XyzWidget()a = WidgetFactory.Create(WidgetType.Xyz) 。然而,就您而言,您从数据库中有一些(数字?)typeId。正如马克正确地写道,你需要有这个 typeId -> className 映射某处

因此,在这种情况下,使用工厂方法的充分理由可能是:“无论如何,我需要某种巨大的 ConvertWidgetTypeIdToClassName select-case-statement,因此使用工厂方法不需要额外的代码加上如果我需要的话,它免费提供工厂方法的优势。”

作为替代方案,您可以将小部件的类名存储在数据库中(无论如何,您可能已经有一些带有主键 typeIdWidgetType 表,对吧?)并创建使用反射的类(如果您的语言允许这种类型的事情)。这有很多优点(例如,您可以使用新的小部件放入 DLL,而不必更改核心 CMS 代码),但也有缺点(例如,数据库中的“魔术字符串”在编译时不会检查;可能的代码注入,取决于谁有权访问该表)。

I think the question you should ask yourself is: Why am I using a Factory method here?

If the answer is "because of A", and A is a good reason, then continue doing it, even if it means some extra code. If the answer is "I don't know; because I've heard that you are supposed to do it this way?" then you should reconsider.

Let's go over the standard reasons for using factories. Here's what Wikipedia says about the Factory method pattern:

[...], it deals with the problem of creating objects (products) without specifying the exact class of object that will be created. The factory method design pattern handles this problem by defining a separate method for creating the objects, whose subclasses can then override to specify the derived type of product that will be created.

Since your WidgetFactory is Private, this is obviously not the reason why you use this pattern. What about the "Factory pattern" itself (independent of whether you implement it using a Factory method or an abstract class)? Again, Wikipedia says:

Use the factory pattern when:

  • The creation of the object precludes reuse without significantly duplicating code.
  • The creation of the object requires access to information or resources not appropriate to contain within the composing object.
  • The lifetime management of created objects needs to be centralised to ensure consistent behavior.

From your sample code, it does not look like any of this matches your need. So, the question (which only you can answer) is: (1) How likely is it that you will need the features of a centralized Factory for your widgets in the future and (2) how costly is it to change everything back to a Factory approach if you need it in the future? If both are low, you can safely drop the Factory method for the time being.


EDIT: Let me get back to your special case after this generic elaboration: Usually, it's a = new XyzWidget() vs. a = WidgetFactory.Create(WidgetType.Xyz). In your case, however, you have some (numeric?) typeId from a database. As Mark correctly wrote, you need to have this typeId -> className map somewhere.

So, in that case, the good reason for using a factory method could be: "I need some kind of huge ConvertWidgetTypeIdToClassName select-case-statement anyway, so using a factory method takes no additional code plus it provides the factory method advantages for free, if I should ever need them."

As an alternative, you could store the class name of the widget in the database (you probably already have some WidgetType table with primary key typeId anyway, right?) and create the class using reflection (if your language allows for this type of thing). This has a lot of advantages (e.g. you could drop in DLLs with new widgets and don't have to change your core CMS code) but also disadvantages (e.g. "magic string" in your database which is not checked at compile time; possible code injection, depending on who has access to that table).

拧巴小姐 2024-08-22 03:25:24

WidgetFactory 方法实际上是从 typeId 枚举到具体类的映射。一般来说,最好能够完全避免枚举,但有时(特别是在 Web 应用程序中)您需要往返于不理解多态性的环境(例如浏览器),并且需要此类措施。

重构 很好地解释了为什么 switch/select case 语句是代码异味,但这主要解决有许多类似开关的情况。

如果您的 WidgetFactory 方法是您打开该特定枚举的唯一位置,我会说您不必担心。你需要把那张地图放在某个地方。

作为替代方案,您可以将映射定义为字典,但代码行数不会显着减少 - 您可以将代码行数减少一半,但复杂程度将保持不变。

The WidgetFactory method is really a mapping from a typeId enumeration to concrete classes. In general it's best if you can avoid enumerations entirely, but sometimes (particularly in web applications) you need to round-trip to an environment (e.g. the browser) that doesn't understand polymorphism and you need such measures.

Refactoring contains a pretty good explanation of why switch/select case statements are code smells, but that mainly addresses the case where you have many similar switches.

If your WidgetFactory method is the only place where you switch on that particular enum, I would say that you don't have to worry. You need to have that map somewhere.

As an alternative, you could define the map as a dictionary, but the amount of code lines wouldn't decrease significantly - you may be able to cut the lines of code in half, but the degree of complexity would stay equivalent.

提笔书几行 2024-08-22 03:25:24

您对工厂模式的应用是正确的。您拥有指示创建 N 种类型中的哪一种的信息。工厂知道如何做到这一点。 (作为私有方法,它有点奇怪。我希望它位于 IWidgetFactory 接口上。)

不过,您的实现将实现与具体类型紧密耦合。如果您改为映射 typeId -> widgetType,您可以使用Activator.CreateInstance(widgetType) 使工厂了解任何小部件类型。

现在,您可以根据需要定义映射:简单的字典、发现(属性/反射)、配置文件等。您必须知道某个地方的所有类型,但您也可以选择组合多个类型来源。

Your application of the factory pattern is correct. You have information which dictates which of N types is created. A factory is what knows how to do that. (It is a little odd as a private method. I would expect it to be on an IWidgetFactory interface.)

Your implementation, though, tightly couples the implementation to the concrete types. If you instead mapped typeId -> widgetType, you could use Activator.CreateInstance(widgetType) to make the factory understand any widget type.

Now, you can define the mappings however you want: a simple dictionary, discovery (attributes/reflection), in the configuration file, etc. You have to know all the types in one place somewhere, but you also have the option to compose multiple sources.

绮筵 2024-08-22 03:25:24

实现工厂的经典方法不是使用巨型开关或 if-ladder,而是使用将对象类型名称映射到对象创建函数的映射。除此之外,这还允许工厂在运行时进行修改。

The classic way of implementing a factory is not to use a giant switch or if-ladder, but instead to use a map which maps object type name to an object creation function. Apart from anything else, this allows the factory to be modified at run-time.

隱形的亼 2024-08-22 03:25:24

无论正确与否,我始终相信使用工厂的时机是根据运行时之前不可用的信息来决定创建哪种对象类型。

您在后续评论中指出小部件类型存储在数据库中。由于您的代码在运行时之前不知道将创建哪些对象,因此我认为这是工厂模式的完全有效的使用。通过拥有工厂,您可以使程序推迟决定使用哪种对象类型,直到实际做出决定为止。

Whether it's proper or not, I've always believed that the time to use a Factory is when the decision of what object type to create will be based upon information that is not available until run-time.

You indicated in a followup comment that the widget type is stored in a database. Since your code does not know what objects will be created until run-time, I think that this is a perfectly valid use of the Factory pattern. By having the factory, you enable your program to defer the decision of which object type to use until the time when the decision can actually be made.

枫林﹌晚霞¤ 2024-08-22 03:25:24

根据我的经验,工厂会不断发展,因此它们的依赖关系就不必增长。如果您看到此映射在其他地方重复,那么您就有理由担心了。

It's been my experience that Factories grow so their dependencies don't have to. If you see this mapping duplicating itself in other places then you have cause for worry.

沫离伤花 2024-08-22 03:25:24

尝试对您的小部件进行分类,也许可以根据它们的功能进行分类。
如果它们中很少有逻辑上相互依赖,则使用单一结构创建它们

try categories your widgets, maybe based on their functionality.
if few of them are logically depending on each other, create them with single construction

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