我的工厂方法是否做得太过分了?
我们核心产品的一部分是使用各种页面小部件的网站 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我认为您应该问自己的问题是:为什么我在这里使用工厂方法?
如果答案是“因为 A”,并且A 是一个很好的理由,然后继续这样做,即使这意味着一些额外的代码。如果答案是“我不知道;因为我听说你应该这样做?”那么你应该重新考虑。
让我们回顾一下使用工厂的标准原因。以下是维基百科对工厂方法模式的描述:
由于您的 WidgetFactory 是
Private
,这显然不是您使用此模式的原因。 “工厂模式”本身怎么样(无论您是使用工厂方法还是抽象类实现它)?再次,维基百科说:从您的示例代码来看,这一切都不符合您的需求。因此,问题(只有您可以回答)是:(1) 您将来的小部件需要集中式工厂功能的可能性有多大,以及 (2) 成本有多大如果您将来需要它,是否可以将所有内容更改回工厂方法?如果两者都较低,您可以暂时安全地放弃 Factory 方法。
编辑:在一般性阐述之后,让我回到您的特殊情况:通常,它是
a = new XyzWidget()
与a = WidgetFactory.Create(WidgetType.Xyz)
。然而,就您而言,您从数据库中有一些(数字?)typeId
。正如马克正确地写道,你需要有这个typeId -> className
映射某处。因此,在这种情况下,使用工厂方法的充分理由可能是:“无论如何,我需要某种巨大的
ConvertWidgetTypeIdToClassName
select-case-statement,因此使用工厂方法不需要额外的代码加上如果我需要的话,它免费提供工厂方法的优势。”作为替代方案,您可以将小部件的类名存储在数据库中(无论如何,您可能已经有一些带有主键
typeId
的WidgetType
表,对吧?)并创建使用反射的类(如果您的语言允许这种类型的事情)。这有很多优点(例如,您可以使用新的小部件放入 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:
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: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 thistypeId -> 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 keytypeId
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).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.
您对工厂模式的应用是正确的。您拥有指示创建 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 useActivator.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.
实现工厂的经典方法不是使用巨型开关或 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.
无论正确与否,我始终相信使用工厂的时机是根据运行时之前不可用的信息来决定创建哪种对象类型。
您在后续评论中指出小部件类型存储在数据库中。由于您的代码在运行时之前不知道将创建哪些对象,因此我认为这是工厂模式的完全有效的使用。通过拥有工厂,您可以使程序推迟决定使用哪种对象类型,直到实际做出决定为止。
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.
根据我的经验,工厂会不断发展,因此它们的依赖关系就不必增长。如果您看到此映射在其他地方重复,那么您就有理由担心了。
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.
尝试对您的小部件进行分类,也许可以根据它们的功能进行分类。
如果它们中很少有逻辑上相互依赖,则使用单一结构创建它们
try categories your widgets, maybe based on their functionality.
if few of them are logically depending on each other, create them with single construction