另一种流行的语言如何在管理与Java / Java EE类似的复杂性时避免使用工厂模式?


23

工厂模式(或至少使用FactoryFactory..)是许多笑话的对接,例如此处

除了具有冗长和“创造性”的名称(如RequestProcessorFactoryFactory.RequestProcessorFactory)之外,如果您必须使用Java / C ++进行编程并且有Abstract_factory_pattern的用例,那么工厂模式是否有根本上的错误?

另一种流行的语言(例如RubyScala)如何在管理相似的复杂性时避免使用它呢?

我要问的原因是,在Java / Java EE生态系统的背景下,我看到的大多数批评都是工厂,但它们从未解释其他语言/框架如何解决它们。



4
问题不在于工厂的使用-这是一个完美的模式。工厂的过度使用在企业级Java世界中尤其普遍。
CodesInChaos 2014年

很不错的笑话链接。我很乐意看到功能语言解释为检索构建香料架的工具。我认为那真的可以把它带回家。
Patrick M

Answers:


28

您的问题被标记为“ Java”,这并不奇怪您为什么要嘲笑Factory模式:Java本身附带了打包好的该模式的滥用。

例如,尝试从文件加载XML文档并对其执行XPath查询。您只需要设置10行代码即可设置工厂和建造者:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

我想知道设计此API的人是否曾经以开发人员的身份工作过,还是只是读书和扔东西而已。我了解他们只是不想自己编写一个解析器,而是将任务留给其他人,但它仍然使实现很丑陋。

由于您要问还有什么替代方法,因此在C#中加载XML文件:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

我怀疑刚孵出的Java开发人员会看到Factory疯狂,并且认为这样做是可以的-如果自己创建Java的天才已经使用它们那么多。

工厂模式和其他任何模式都是工具,每种模式都适合特定的工作。如果将它们应用于工作,那么它们将不适合您使用,因此代码一定很丑陋。


1
请注意,您的C#替代品正在使用较新的LINQ to XML-旧的DOM替代品将看起来像这样:(XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); 总的来说,LINQ to XML更加容易使用,尽管主要是在其他领域。)这是KB文章我发现,采用了MS推荐的方法:support.microsoft.com/kb/308333
Bob的

36

人们常常误解正在发生的事情(包括许多笑声)。
并不是很多人(也许甚至大多数人)使用工厂模式本身就很糟糕。

毫无疑问,这源于编程(和模式)的教授方式。告诉小学生(通常称自己为“学生”)“使用模式Y创建X”,经过几次迭代后认为这是解决任何编程问题的方法。
因此,他们开始在学校中应用碰巧喜欢的特定模式来对抗任何事物,无论它是否合适。

不幸的是,其中包括大学教授,他们撰写有关软件设计的书。
最终的结果是我不得不维护这样一个系统,我对此非常不满意(这个人甚至拥有几本有关面向对象设计的书,以他的名字命名,并且在一家主要大学的CS系任教) )。
它基于3层模式,每个层本身都是3层系统(必须解耦...)。在两侧的每组层之间的接口上,有一个工厂来生产将数据传输到另一层的对象,还有一个工厂来生产对象以将接收到的对象转换为属于接收层的对象。
对于每个工厂,都有一个抽象工厂(谁知道,工厂可能必须更改,然后您不希望必须更改调用代码...)。
整个混乱当然是完全没有记载的。

系统的数据库标准化为第5个正常形式(我不告诉你)。

该系统实质上只是一个通讯录,可用于记录和跟踪传入和传出的文档,它在C ++中具有100MB的代码库和50多个数据库表。使用直接连接到运行该软件的计算机的打印机,打印500个套用信将花费长达72小时。

这就是为什么人们嘲笑模式,尤其是人们一心一意地专注于一个特定的模式。


40
另一个原因是,“四人帮”模式中的某些功能实际上是冗长的破解,可以用具有一流功能的语言轻松完成,这使它们成为不可避免的反模式。“策略”,“观察者”,“工厂”,“命令”和“模板方法”模式是将函数作为参数传递的技巧。“访问者”是一种用于对总和类型/变量/标记的并集执行模式匹配的黑客。有些人对许多其他语言中的琐碎事物不屑一顾,这导致人们嘲笑C ++,Java和类似语言。
2014年

7
谢谢你的轶事。您能否也详细说明一下“替代方案”?问题的一部分,所以我们也不会变得那样吗?
菲利普2014年

1
@Doval你能告诉我当只能传递函数时如何从执行的命令或调用的策略中返回值吗?而且,如果您说闭包,那么请记住,它与创建类完全相同,只是更为明确。
欣快2014年

2
@Euphoric我没看到问题,您能详细说明吗?无论如何,它们不是一回事。您可能会说,具有单个字段的对象与变量的指针/引用完全相同,或者整数与(真实)枚举完全相同。在实践中存在差异:比较功能没有任何意义;我还没有看到匿名类的简洁语法。并且具有相同参数和返回类型的所有函数都具有相同的类型(与具有不同名称的类/接口不同,即使它们是相同的,它们也具有不同的类型。)
Doval 2014年

2
@Euphoric正确。如果有办法在没有副作用的情况下完成工作,那将被认为是不好的功能风格。一个简单的替代方法是使用求和类型/标记并集返回N种值之一。无论如何,我们假设没有实际的方法。它与课程不同。实际上,它是一个接口(从OOP角度来看)。不难看出您是否认为具有相同签名的任何一对函数都是可互换的,而两个类即使具有完全相同的内容也永远不可互换。
Doval 2014年

17

工厂具有许多优点,可以在某些情况下进行优雅的应用程序设计。一种是可以通过创建工厂来设置以后要创建的对象的属性,然后将该工厂移交给其他人。但是通常您实际上并不需要这样做。在这种情况下,使用Factory只会增加额外的复杂性,而实际上并没有给您任何回报。让我们以这个工厂为例:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

Factory模式的一种替代方法是非常相似的Builder模式。主要区别在于,由工厂创建的对象的属性是在初始化工厂时设置的,而生成器是使用默认状态初始化的,而所有属性均在此后设置。

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

但是当过度工程成为您的问题时,用制造商替换Factory可能不会带来太大的改善。

这两种模式中最简单的替代方法当然是使用带有new运算符的简单构造函数来创建对象实例:

Widget widget = new ColoredWidget(COLOR_RED);

但是,构造函数在大多数面向对象的语言中有一个关键的缺点:它们必须返回该确切类的对象,而不能返回子类型。

如果您需要在运行时选择子类型,但又不想为此创建一个全新的Builder或Factory类,则可以改用工厂方法。这是类的静态方法,该方法返回该类或其子类之一的新实例。不维护任何内部状态的工厂通常可以用这种工厂方法代替:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Java 8中的新功能是方法引用,它使您可以像在无状态工厂中那样传递方法。方便地,任何接受方法引用的对象也将接受实现相同功能接口的任何对象,该对象也可以是具有内部状态的成熟Factory,因此,当您看到这样做的原因时,可以稍后轻松地引入它们。


2
创建后通常也可以配置工厂。与构建器的主要区别afaik是,构建器通常用于创建单个复杂实例,而工厂用于创建许多类似实例。
Cephalopod

1
Thanks(+1),但是答案无法解释其他PL如何解决该问题,但是仍然感谢您指出Java 8方法引用。
senseiwu

1
我经常认为,在面向对象的框架中将实际的对象构造限制为所讨论的类型是有意义的,并且foo = new Bar(23);等效于foo = Bar._createInstance(23);。对象应该能够使用私有getRealType()方法可靠地查询其自己的类型,但是对象应该能够指定要由外部调用返回的超类型getType()。外部代码不必关心是否new String("A")实际调用String[ 返回了]实例,而不是返回[ SingleCharacterString] 实例。
supercat 2014年

1
当我需要根据上下文选择子类时,我使用了许多静态方法工厂,但是差异对于调用者来说应该是不透明的。例如,我有一系列图像类,它们都包含draw(OutputStream out)但生成略有不同的html,静态方法工厂为情况创建了正确的类,然后调用者可以只使用draw方法。
Michael Shopsin 2014年

1
@supercat您可能有兴趣查看Objective-C。对象的构造通常由简单的方法调用组成[[SomeClass alloc] init]。可以返回完全不同的类的实例或另一个对象(例如缓存的值)
axelarge 2014年

3

在适当的情况下,几乎任何面向对象的语言都可以发现一种或另一种工厂。有时,您只是需要一种方法,可以根据一个简单的参数(如字符串)选择要创建哪种对象。

有些人太过分了,尝试构建代码,除在工厂内部外,无需调用构造函数。当您拥有工厂工厂时,事情开始变得荒谬。

当我学习Scala并且不了解Ruby时,给我留下了深刻的印象,但我相信这是完全一样的,因为该语言具有足够的表现力,程序员并不总是尝试将“管道”工作推向外部配置文件。在Scala中,您使用混合了特征的对象,而不是试图创建工厂工厂来将类以不同的配置连接在一起,而不是被诱惑。在语言中创建简单的DSL相对容易,这对于以Java过度设计的任务来说是很容易的。

而且,正如其他评论和答案所指出的那样,闭包和一流的功能消除了对许多模式的需求。因此,我相信随着C ++ 11和Java 8的广泛采用,许多反模式都将消失。


谢谢(+1)。您的回答解释了我对Scala如何避免它的一些疑问。老实说,难道您不认为一旦Scala在企业级别上普及到Java的程度至少一半,框架,模式,付费应用服务器等的大军就会冒出来吗?
senseiwu

我认为,如果Scala超越Java,那是因为人们更喜欢架构软件的“ Scala方式”。让我印象最深的是,Java继续采用更多促使人们转向Scala的功能,例如Java 5的泛型和Java 8的lambda和stream,它们有望最终导致程序员放弃反模式,而当这些反模式出现时,功能不可用。不管语言如何,总会有FactoryFactory的拥护者。显然,有些人喜欢那些架构,否则它们不会那么普遍。
Karl Bielefeldt 2014年

2

通常,这种趋势是Java程序被过度工程化[需要引用]。拥有许多工厂是过度设计的最常见症状之一。这就是为什么人们取笑那些。

特别是,Java在工厂中遇到的问题是在Java中a)构造函数不是函数,并且b)函数不是一等公民。

想象一下,您可以编写这样的内容(让它Function成为JRE的知名接口)

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

参见,没有工厂接口或类。许多动态语言具有一流的功能。,,这在Java 7或更早版本中是不可能的(通过工厂实现相同的功能留给读者练习)。

因此,替代方案不是“使用不同的模式”,而是“使用具有更好对象模型的语言”或“不要过度设计简单问题”。


2
这甚至都没有试图回答所问的问题:“还有哪些选择?”
gnat 2014年

1
阅读第二段之后,您将进入“其他语言的用法”部分。(PS:另一个答案也没有显示其他方法)
Cephalopod 2014年

4
@gnat他不是间接说替代方法是使用一流的功能吗?如果您想要的是可以创建对象的黑匣子,那么您的选择要么是工厂,要么是函数,而工厂只是变相的函数。
2014年

3
“在许多动态语言中”有点误导,因为实际上需要的是具有一流功能的语言。“动态”与此正交。
Andres F.

是的,但这是动态语言中经常发现的属性。我在回答的开头提到了对一流功能的需求。
Cephalopod
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.