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

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

使用它们的考虑因素是什么?

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

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

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

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

有帮助吗?

解决方案

从第 108 页开始 设计模式:Gamma、Helm、Johnson 和 Vlissides 的可重用面向对象软件的元素。

当以下情况时使用工厂方法模式

  • 类无法预测它必须创建的对象的类
  • 一个类希望它的子类指定它创建的对象
  • 类将责任委托给几个帮助器子类之一,并且您希望本地化哪个帮助器子类是委托的知识

其他提示

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

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对象。

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

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

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

静态工厂方法的优点:

  1. 他们有名字。
  2. 每次调用它们时都不需要创建新对象。
  3. 他们可以返回其返回类型的任何子类型的对象。
  4. 它们减少了创建参数化类型实例的详细程度。
  5. 静态工厂方法的缺点:

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

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

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

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

"注意静态工厂方法与Factory Method模式不同 来自设计模式 [Gamma95,p。 107]。静态工厂方法描述于 此项目在“设计模式”中没有直接等效项。“

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

工厂有可能以缓存为例。

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

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

使用构造函数进行切割路径。它是一系列线条和弧线,用于定义切割路径。虽然一系列的线和弧可以是不同的并且具有不同的坐标,但是通过将列表传递给构造函数可以很容易地处理它。

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

除了“有效的java”(如另一个答案中所述),另一本经典书籍也暗示:

  

首选静态工厂方法(带有描述参数的名称)来重载构造函数。

EG。不要写

Complex complex = new Complex(23.0);

但是写一下

Complex complex = Complex.fromRealNumber(23.0);

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

  

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

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

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

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

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

  4. Microsoft的一些构造函数指南

      

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

      

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

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top