我用我的工厂方法过度了吗?

时间:2009-12-17 09:38:21

标签: language-agnostic design-patterns factory-method

我们的核心产品的一部分是一个网站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,这意味着工厂方法相当长。每次我创建一个新类型的小部件时,我会在方法中添加另外几行,并且在我脑海中发出一点警报,这可能不是最好的做事方式。我倾向于忽略那个警报,但它的声音越来越大。

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

7 个答案:

答案 0 :(得分:15)

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

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

让我们回顾一下使用工厂的标准原因。以下是有关Factory方法模式的Wikipedia says

  

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

由于您的 WidgetFactory Private,这显然不是您使用此模式的原因。 “工厂模式”本身怎么样(与使用Factory方法还是抽象类实现它无关)?同样,Wikipedia says

  

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

     
      
  • 对象的创建阻止了重用,而不会显着重复代码。
  •   
  • 创建对象需要访问不适合包含在撰写对象中的信息或资源。
  •   
  • 需要集中创建对象的生命周期管理,以确保一致的行为。
  •   

从您的示例代码中看,它看起来不符合您的需求。所以,问题(只有你可以回答)是:(1)你将来为你的小部件需要集中工厂的功能的可能性有多大;(2)你的成本是多少如果将来需要它,可以将所有内容改回工厂方法吗?如果两者都很低,您可以暂时放弃Factory方法。


编辑:在这个通用的阐述之后,让我回到你的特殊情况:通常,它是a = new XyzWidget()a = WidgetFactory.Create(WidgetType.Xyz)。但是,在您的情况下,您从数据库中获得了一些(数字?)typeId。正如马克写的那样,你需要将typeId -> className地图放在某处

因此,在这种情况下,使用工厂方法的充分理由可能是:“无论如何我需要某种巨大的ConvertWidgetTypeIdToClassName选择案例语句,因此使用工厂方法不需要额外的代码 plus 它提供了免费的工厂方法优势,如果我需要的话。“

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

答案 1 :(得分:5)

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

Refactoring包含了很好的解释,为什么switch / select case语句是代码味道,但这主要解决了你有许多类似开关的情况。

如果你的WidgetFactory方法是唯一的地方你打开那个特定的枚举,我会说你不必担心。你需要在某处拥有该地图。

作为替代方案,您可以将地图定义为字典,但代码行的数量不会显着减少 - 您可以将代码行减少一半,但复杂程度将保持相同。

答案 2 :(得分:4)

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

但是,您的实现将实现与具体类型紧密结合在一起。如果您改为映射typeId -> widgetType,则可以使用Activator.CreateInstance(widgetType)使工厂了解任何窗口小部件类型。

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

答案 3 :(得分:2)

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

答案 4 :(得分:2)

无论是否合适,我一直认为使用工厂的时间是在创建什么对象类型的决定将基于运行时之前不可用的信息时。

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

答案 5 :(得分:1)

根据我的经验,工厂成长,所以他们的依赖不需要。如果你看到这个映射在其他地方重复,那么你有理由担心。

答案 6 :(得分:0)

尝试根据功能对小部件进行分类。 如果它们中很少有逻辑上相互依赖,那么用单一构造创建它们