工厂模式是否违反了开放/关闭原则?


14

为什么此ShapeFactory使用条件语句来确定要实例化的对象。如果将来要添加其他类,是否不必修改ShapeFactory?为什么这不违反开放封闭原则?

工厂模式设计

ShapeFactory设计


2
您确切指的是哪种“工厂模式”?通常,工厂是用于实例化对象的任何对象或方法。然后是这种一般思想的特定变体,例如“抽象工厂模式”,其中每个工厂实例代表一个特定的选择面板-通常通过子类而非条件进行管理。
阿蒙2015年


3
感谢您提供的信息,这可以解决很多问题。无疑,这是工厂模式的示例,但不是通常与工厂模式关联的抽象工厂模式的示例。那篇文章中的代码是有问题的,我不希望在实际代码中看到类似的东西。
阿蒙2015年

@ArmonSafai:您经常链接此博客文章,但您并没有真正解释原因。我们都以某种方式不了解这种模式吗?就像您一样,我们也有Google。
罗伯特·哈维

1
@RobertHarvey我正在链接此博客文章,以显示该页面中的工厂模式如何使用条件

Answers:


20

常规的面向对象的知识是避免使用if语句,而将其替换为抽象类的子类中的重写方法的动态分配。到现在为止还挺好。

但是工厂模式的重点是减轻您不必了解各个子类的麻烦,并且只使用抽象的超类。这样的想法是,工厂比您更了解要实例化哪个特定类,并且您最好只使用超类发布的方法。这通常是真实的并且是有价值的模式。

因此,编写工厂类无法放弃这些if语句。这将把选择特定类的负担转移给调用者,这正是该模式应避免的事情。并非所有原理都是绝对的(实际上,没有原理是绝对的),并且如果您使用此模式,您将假定从中受益大于不使用if


2
完全没有可能创建一个工厂模式if。有关如何实现此目标的简单示例,请参见@BЈовић的答案。不赞成投票。
David Arno


11
@DavidArno当然,选择具体课程有多种方法。服务定位器是一个,可配置的IoC容器是另一个。这些只是实现细节;它们不会影响Killian的主要信息,即Factory使调用者不必决定要实例化哪个具体类。不要陷入细节。
罗伯特·哈维

1
绝不回答问题的出色陈述。
马丁·马特

1
@ R.Schmitz我认为您的假设不正确。我认为许多人从OP中漏掉了这个问题:“如果将来要添加其他类,我们是否不必修改ShapeFactory?” 显然,OP困惑于此模式违反了OCP,因为要添加新功能,您必须修改现有代码。这个问题的正确答案可以在我的答案中找到。简短的答案:您不用理会该代码,而是将Abstract Factory模式应用于EXTEND(而不是修改)现有功能。因此,Kilian的回答无法解决该问题。
hfontanez '19

5

该示例可能使用条件语句,因为它是最简单的。一个更复杂的实现可能使用映射或配置,或者(如果您真的很想想)使用某种类型的注册表可以在其中注册自己的注册表。但是,如果类的数量很少且不经常更改,则使用条件语句没有任何问题。

严格说来,将来扩展条件以增加对新子类的支持实际上将违反开放/封闭原则。“正确”的解决方案是使用相同的界面创建一个新工厂。就是说,遵守O / C原则应始终与其他设计原则(如KISS和YAGNI)权衡。

也就是说,显示的代码显然是示例代码,旨在显示工厂的概念,仅此而已。例如,像示例一样,返回null确实是一种不好的风格,但是更复杂的错误处理只会掩盖这一点。示例代码不是生产质量代码,您不应期望任何代码。


您能解释一下地图/配置/注册表如何工作吗?
Armon Safai 2015年


在A ++中,自注册工厂在静态库中的C ++中是不可能的,因为未使用(即已使用odr的)全局变量被工具链丢弃。
void.pointer

@ArmonSafai阅读了此书以进一步了解goo.gl/RYuNSM
AZ_

2

模式本身不违反开放/封闭原则(OCP)。但是,如果我们错误地使用该模式,则会违反OCP。

这个问题的简单答案如下:

  1. 使用工厂方法模式创建基本功能。
  2. 使用抽象工厂模式扩展您的功能

在提供的示例中,基本功能支持三种形状:圆形,矩形和正方形。假设您将来需要支持Triangle,Pentagon和Hexagon。要做到这一点而又不违反OCP,您必须创建一个额外的工厂来支持您的新形状(称为AdvancedShapeFactory),然后使用AbstractFactory决定您需要创建哪个工厂才能创建所需的形状。


我非常喜欢自注册工厂解决方案(在没有真正可配置的IoC容器的情况下最好)的情况,因为否则我们从您的建议中得到的基本上是工厂工厂,那就是事情变得过于复杂。
Nom1fan

1

如果您在谈论抽象工厂模式,那么决策通常不在工厂本身中,而在应用程序代码中。该代码选择要实例化的具体工厂,并传递给将使用该工厂生产的对象的客户端代码。请在此处查看Java示例的结尾:https : //en.wikipedia.org/wiki/Abstract_factory_pattern

决策不一定意味着if陈述。它可以从配置文件中读取具体的Factory类型,从映射结构中派生出来,等等。



如果我(调用方)正在决定要实例化哪个具体类,那么为什么还要烦恼Abstract Factory?
罗伯特·哈维

请定义“呼叫者”。正如我在回答中所描述的,有一个全局应用程序代码,然后是需要使用Factory生成对象的代码。尽管后者确实需要不知道要实例化的具体类,但其他一些上下文代码必须了解它并对其进行新...
guillaume31

0

如果您考虑使用此工厂在类级别进行“打开-关闭”操作,则在系统中创建其他“打开-关闭”类,例如,如果您有其他采用一个Shape并计算面积的类(典型示例),则该类为“打开关闭”,因为它可以不经修改就为新型形状计算面积。然后,您有另一个绘制形状的类,另一个具有N个形状并返回较大形状的类,您通常可以认为系统中处理形状的其他类是Open-Close(至少与形状有关)。从设计的角度来看,工厂使系统的其余部分处于“开-关”状态,而偏离工厂本身的方向是“不开/关”。

当然,您也可以通过某种动态加载使此工厂开-关,并且整个系统可以是开-关(例如,您可以在类路径中添加一些罐子来添加新形状)。您需要评估这种额外的复杂性是否值得,具体取决于您所构建的系统,并非所有系统都需要可插拔功能,并且并非所有系统都需要完全打开/关闭。


0

作为Liskov替换原则的开闭原则适用于类树和继承层次结构。在您的示例中,工厂类不在其实例化的类的族树中,因此它不会违反这些规则。如果在Shape基类中实现了GetShape(或更恰当地命名为CreateShape),则会出现冲突。


-2

这完全取决于您如何实施。您可以std::map用来保存指向创建对象的函数的函数指针。这样就不会违反打开/关闭原理。或开关/外壳。

无论如何,如果您不喜欢工厂模式,则可以始终使用依赖项注入。


6
开关/案例比有条件的情况如何?如果您实际上需要注册不同实现的注册表(例如在某些DI容器实现中),则使用map / dict / table将代码表示为数据是很好的。但是,对于大多数工厂来说,不必具有相同类型的不同回调!我不太明白你为什么这么建议。另外,许多DI容器都是根据工厂对象实现的,因此建议使用DI代替工厂似乎有些循环。
阿蒙2015年

1
@amon我打算使用其他类型的DI-而不是工厂。
2015年


1
您的工厂将如何决定使用哪个指针?最终,您必须做出决定。
whatsisname 2015年

@ArmonSafai ???
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.