Language agnostic 我是不是用工厂的方法做得太过分了?

Language agnostic 我是不是用工厂的方法做得太过分了?,language-agnostic,design-patterns,factory-method,Language Agnostic,Design Patterns,Factory Method,我们核心产品的一部分是一个网站CMS,它利用各种页面小部件。这些小部件负责显示内容、列出产品、处理事件注册等。每个小部件由派生自基本小部件类的类表示。呈现页面时,服务器从数据库中获取页面的小部件,然后创建正确类的实例。工厂的方法对吗 Private Function WidgetFactory(typeId) Dim oWidget Select Case typeId Case widgetType.ContentBlock Set oW

我们核心产品的一部分是一个网站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种,这意味着工厂方法相当长。每次我创建一个新类型的小部件时,我都会在这个方法中再添加几行代码,我的脑海中就会响起一个小小的警报,也许这不是最好的方法。我倾向于忽略那个警报,但它越来越响了


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

尝试分类您的小部件,可能基于它们的功能。
如果它们中很少有逻辑上相互依赖的,那么使用单个构造创建它们

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

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

让我们回顾一下使用工厂的标准原因。以下是Factory方法模式的情况:

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

由于WidgetFactory是私有的,这显然不是使用此模式的原因。“工厂模式”本身如何(与您是使用工厂方法还是抽象类实现它无关)?再说一遍:

在以下情况下使用factory模式:

  • 对象的创建排除了重用,而不会显著重复代码
  • 创建对象需要访问不适合包含在组成对象中的信息或资源
  • 创建对象的生命周期管理需要集中化,以确保行为一致
从您的示例代码来看,这一切似乎都不符合您的需要。因此,问题(只有你能回答)是:(1)你将来需要一个集中工厂为你的小部件提供功能的可能性有多大;(2)如果你将来需要,把所有东西都改回工厂方法的成本有多高?如果两者都很低,您可以暂时安全地放弃Factory方法


编辑:在这个一般性的细化之后,让我回到您的特例:通常,它是
a=new XyzWidget()
vs.
a=WidgetFactory.Create(WidgetType.Xyz)
。然而,在您的例子中,您从数据库中获得了一些(数字?
typeId
)。正如Mark正确写的那样,您需要将这个
typeId->className
映射到某个地方

因此,在这种情况下,使用工厂方法的好理由可能是:“我需要某种巨大的
ConvertWidgetTypeIdToClassName
select case语句,因此使用工厂方法不需要额外的代码,而且如果我需要的话,它免费提供工厂方法的优势。”


或者,您可以将小部件的类名存储在数据库中(您可能已经有了一些主键为
typeId
WidgetType
表,对吧?),并使用反射创建类(如果您的语言允许这种类型的东西)。这有很多优点(例如,您可以使用新的小部件插入DLL,并且不必更改核心CMS代码),但也有缺点(例如,在编译时未检查数据库中的“magic string”;可能的代码注入,取决于谁有权访问该表).WidgetFactory方法实际上是从typeId枚举到具体类的映射。一般来说,最好完全避免枚举,但有时(特别是在web应用程序中),您需要往返到不了解多态性的环境(例如浏览器),并且需要这样的措施

对switch/select case语句为什么是代码气味进行了很好的解释,但这主要是针对有许多类似开关的情况

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


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

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

无论是否正确,我始终认为使用工厂的时间是根据运行时之前不可用的信息来决定要创建什么对象类型

您在后续注释中指出小部件类型存储在数据库中。因为在运行时之前,您的代码不知道将创建什么对象,所以我认为这是Factory模式的一个非常有效的用法。通过使用工厂,您可以使您的程序推迟决定使用哪种对象类型,直到实际可以做出决定为止。