构造函数与工厂方法[关闭]


180

在对类建模时,首选的初始化方式是:

  1. 构造函数,或
  2. 工厂方法

使用这两种方法的考虑因素是什么?

在某些情况下,我更喜欢使用工厂方法,如果无法构造该对象,则该方法返回null。这使代码更简洁。我可以简单地检查返回的值是否不为null,然后再执行其他操作,这与从构造方法中引发异常形成了对比。(我个人不喜欢例外)

说,我有一个类的构造函数,它需要一个id值。构造函数使用此值从数据库填充类。如果不存在具有指定ID的记录,则构造函数将引发RecordNotFoundException。在这种情况下,我将必须将所有此类的构造包含在try..catch块中。

与此相反,我可以在这些类上使用静态工厂方法,如果未找到该记录,则该方法将返回null。

在这种情况下,构造方法或工厂方法哪种方法更好?

Answers:


66

从第108页的“ 设计模式:Gamma,Helm,Johnson和Vlissides的可重用的面向对象软件的元素”中。

在以下情况下使用“工厂方法”模式

  • 一个类无法预期它必须创建的对象的类
  • 一个类希望其子类指定其创建的对象
  • 类将职责委派给几个帮助程序子类之一,而您想定位哪个帮助程序子类是委托的知识

21
静态工厂方法不同于GoF设计模式-工厂方法模式。 stackoverflow.com/questions/929021/...
SREE拉玛

请不要与GoF设计模式的Factory方法模式进行比较。
Sree Rama

137
这对我什么都没解释
Sushant 2014年

@Sushant,为什么会这样?
PaulD 2014年

2
这个答案不能回答问题,只是传递信息以阅读以理解/解释这个概念……更多是评论。
Crt

201

问问自己它们是什么,为什么我们要它们。它们都在那里创建对象的实例。

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

到目前为止没有差异。现在,假设我们有各种学校类型,我们想从使用ElementarySchool切换到HighSchool(后者是从ElementarySchool派生的,或实现与ElementarySchool相同的接口ISchool)。代码更改为:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

如果是接口,我们将:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

现在,如果您在多个地方都有此代码,则可以看到使用factory方法可能非常便宜,因为一旦更改了factory方法,就可以完成操作(如果我们将第二个示例与接口一起使用)。

这是主要的区别和优势。当您开始处理复杂的类层次结构并且想要从这样的层次结构动态创建类的实例时,您将获得以下代码。然后,工厂方法可以使用一个参数,该参数告诉方法要实例化的具体实例。假设您有一个MyStudent类,并且需要实例化相应的ISchool对象,以便您的学生是该学校的成员。

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

现在,您的应用程序中有了一个地方,其中包含确定要为不同的IStudent对象实例化的ISchool对象的业务逻辑。

所以-对于简单的类(值对象等),构造函数就可以了(您不想对应用程序进行过度设计),但是对于复杂的类层次结构,工厂方法是首选的方法。

这样,您将遵循四本书《程序到接口,而不是实现》一书中的第一个设计原则。


2
即使您认为这是一个简单的类,也有可能有人需要扩展您的简单类,因此工厂方法仍然更好。例如,您可以从ElementarySchool开始,但后来有人(包括您自己)可以通过PrivateElementarySchool和PublicElementarySchool对其进行扩展。
2016年

10
这应该是公认的答案
am05mhz

2
@David,很好的答案,但是您可以扩展一个示例,其中每个接口实现都可能需要不同的参数来进行构造。这是一个愚蠢的例子:IFood sandwich = new Sandwich(Cheese chz, Meat meat);IFood soup = new Soup(Broth broth, Vegetable veg);如何能工厂,或者Builder帮助吗?
布赖恩

1
我刚刚阅读了关于工厂使用目的的其他三种解释,这是最终让我“点击”的一种解释。谢谢!
Daniel Peirano

为什么这是不被接受的答案?
托马斯

74

您需要阅读(如果可以访问)有效的Java 2 项目1:考虑静态工厂方法而不是构造函数

静态工厂方法的优点:

  1. 他们有名字。
  2. 不需要在每次调用它们时创建一个新对象。
  3. 他们可以返回其返回类型的任何子类型的对象。
  4. 它们减少了创建参数化类型实例的冗长性。

静态工厂方法的缺点:

  1. 当仅提供静态工厂方法时,没有公共或受保护构造函数的类不能被子类化。
  2. 它们不易与其他静态方法区分开

4
在我看来,这似乎是Java中的一个严重错误,然后是一个普通的OOD问题。有许多OO语言甚至没有构造函数,但是子类化就很好。
约尔格W¯¯米塔格

1
@cherouvim为什么大多数情况下使用构造函数编写代码,如果( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Asif Mushtaq

好点。它是特定于Java的。可以使用一种语言功能,使工厂方法与其他静态方法区分开。
OCDev

30

默认情况下,应该首选构造函数,因为它们更易于理解和编写。但是,如果您特别需要将对象的构造细节与客户端代码所理解的语义相分离,那么最好使用工厂。

构造函数和工厂之间的区别类似于变量和指向变量的指针。还有另一个间接级别,这是一个缺点。但是还有另一种灵活性,这是一个优势。因此,在做出选择时,建议您进行成本与收益分析。


17
因此,(TDD样式)您将从构造函数开始,这是完成工作的最简单方法。然后,一旦开始闻到代码的味道,然后重构到工厂(例如重复的条件逻辑确定要调用哪个构造函数)?
AndyM

1
非常重要的一点。一项对工厂和构造函数进行比较的用户研究发现,非常有意义的结果表明工厂不利于API的可用性:“与构造函数相比,与构造函数相比,用户用工厂构造对象的时间要多得多(p = 0.005)” [API设计中的工厂模式:可用性评估 ]。
mdeff

12

仅在需要通过对象创建进行额外控制的情况下才使用工厂,而这是构造函数无法做到的。

工厂有可能进行缓存。

使用工厂的另一种方法是在您不知道要构造的类型的情况下。通常在插件工厂场景中会看到这种用法,其中每个插件都必须派生自基类或实现某种接口。工厂创建从基类派生或实现接口的类的实例。


11

摘自“有效Java”,第二版,第1项:考虑静态工厂方法而不是构造函数,p。5:

“请注意,静态工厂方法与设计模式 [Gamma95,第107页]中的工厂方法模式不同。此项中描述的静态工厂方法在设计模式中没有直接等效项。”


10

除了“有效的Java”(如另一个答案中所述)外,另一本经典著作还建议:

首选静态工厂方法(名称描述参数)而不是重载的构造函数。

例如。不要写

Complex complex = new Complex(23.0);

但是写

Complex complex = Complex.fromRealNumber(23.0);

本书甚至建议将Complex(float)构造函数设为私有,以强制用户调用静态工厂方法。


2
阅读本书的这一部分将我带到这里
Purple Haze

1
@Bayrem:我也是,我最近在重新阅读它,并认为我应该将其添加到答案中。
blue_note

1
在一个相关的说明,您可能会发现有用的一些命名约定制定了由java.time框架有关的命名from…to…parse…with…,等。请记住,java.time类是构建为不可变的,但是其中某些命名约定也可能对可变类有用。
罗勒·布尔克

7

来自CAD / CAM应用程序的具体示例。

可以通过使用构造函数来创建剪切路径。它是定义切割路径的一系列直线和弧线。尽管直线和弧线的序列可以不同并且具有不同的坐标,但是通过将列表传递到构造函数中可以轻松处理。

可以通过使用工厂来制作形状。因为尽管有一个形状类别,但是根据形状的类型,每个形状的设置将有所不同。在用户做出选择之前,我们不知道要初始化什么形状。


5

说,我有一个类的构造函数,它需要一个id值。构造函数使用此值从数据库填充类。

这个过程绝对应该在构造函数之外。

  1. 构造函数不应访问数据库。

  2. 构造函数的任务和原因是初始化数据成员并使用传递给构造函数的值建立类不变性

  3. 对于其他所有方法,更好的方法是使用静态工厂方法,或者在更复杂的情况下使用单独的工厂构建器类。

Microsoft的一些构造方法指南

在构造函数中完成最少的工作。除了捕获构造函数参数外,构造函数不应做太多工作。任何其他处理的成本都应延迟到需要时再进行。

如果所需操作的语义没有直接映射到新实例的构造,请考虑使用静态工厂方法而不是构造函数。


2

有时您在创建对象时必须检查/计算一些值/条件。如果它可以引发异常,则constructro是非常糟糕的方法。因此,您需要执行以下操作:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

所有其他计算都在init()中。但是只有作为开发人员的您才能真正了解此init()。当然,几个月后,您便会忘记它。但是,如果您有工厂-只需用一种方法来完成所有需要的工作,就可以从直接调用中隐藏此init()-因此没有问题。通过这种方法,创建和内存泄漏都不会出现问题。

有人告诉您有关缓存的信息。很好。但是您还必须记住有关Flyweight模式的信息,该模式很适合与Factory方法一起使用。

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.