201

建模类时,首选的初始化方式是什么:

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

使用其中任何一个的考虑因素是什么?

在某些情况下,如果无法构造对象,我更喜欢使用返回 null 的工厂方法。这使代码整洁。在采取替代操作之前,我可以简单地检查返回的值是否不为空,而不是从构造函数中抛出异常。(我个人不喜欢例外)

比如说,我在一个需要 id 值的类上有一个构造函数。构造函数使用此值从数据库中填充类。在具有指定 id 的记录不存在的情况下,构造函数会抛出 RecordNotFoundException。在这种情况下,我将不得不将所有此类类的构造包含在 try..catch 块中。

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

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

4

10 回答 10

241

问问自己它们是什么以及我们为什么拥有它们。他们都在那里创建对象的实例。

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

现在,如果您在多个地方都有此代码,您会看到使用工厂方法可能非常便宜,因为一旦您更改了工厂方法,您就完成了(如果我们使用带有接口的第二个示例)。

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

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

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

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

这样,您就可以遵循四本书“程序到接口,而不是实现”中的第一个设计原则。

于 2009-03-10T05:18:28.923 回答
79

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

静态工厂方法的优点:

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

静态工厂方法的缺点:

  1. 当仅提供静态工厂方法时,不能对没有公共或受保护构造函数的类进行子类化。
  2. 它们不容易与其他静态方法区分开来
于 2009-03-10T07:26:10.053 回答
63

来自Gamma、Helm、Johnson 和 Vlissides的 Design Patterns: Elements of Reusable Object-Oriented Software 第 108 页。

使用工厂方法模式

  • 一个类不能预测它必须创建的对象类
  • 一个类希望它的子类指定它创建的对象
  • 类将责任委托给几个助手子类之一,并且您希望本地化哪个助手子类是委托的知识
于 2009-03-10T04:43:27.213 回答
35

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

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

于 2009-03-10T04:41:21.633 回答
14

仅当您需要对对象创建进行额外控制时才使用工厂,这是构造函数无法完成的。

例如,工厂可以进行缓存。

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

于 2009-03-10T07:12:38.323 回答
11

引用自“Effective Java”,第 2 版,第 1 项:考虑静态工厂方法而不是构造函数,p。5:

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

于 2010-11-27T22:50:50.913 回答
10

除了“有效的java”(在另一个答案中提到),另一本经典书籍也建议:

与重载的构造函数相比,更喜欢静态工厂方法(具有描述参数的名称)。

例如。不要写

Complex complex = new Complex(23.0);

而是写

Complex complex = Complex.fromRealNumber(23.0);

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

于 2018-09-04T14:47:04.597 回答
8

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

将使用构造函数制作切割路径。它是一系列定义切割路径的线和弧。虽然一系列线和弧可以不同并且具有不同的坐标,但通过将列表传递给构造函数可以轻松处理。

将使用工厂制作形状。因为虽然有一个形状类,但每个形状的设置都会有所不同,具体取决于它的形状类型。在用户做出选择之前,我们不知道要初始化什么形状。

于 2009-03-10T12:30:42.350 回答
5

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

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

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

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

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

来自 Microsoft 的一些构造函数指南

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

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

于 2015-09-22T10:17:06.157 回答
2

有时您必须在创建对象时检查/计算一些值/条件。如果它可以抛出异常-constructro 是非常糟糕的方法。所以你需要做这样的事情:

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

所有额外的计算都在 init() 中。但只有作为开发人员的你才真正了解这个 init()。当然,几个月后你就会忘记它。但是,如果你有一个工厂——只需在一种方法中完成所有你需要的操作,即从直接调用中隐藏这个 init()——所以没有问题。使用这种方法在创建和内存泄漏方面没有问题。

有人告诉你关于缓存。很好。但是您还必须记住 Flyweight 模式,它非常适合与 Factory 方式一起使用。

于 2018-03-29T14:34:24.893 回答