构造函数与工厂方法

Hem*_*jak 172 oop ooad

建模类时,首选的首选方法是什么:

  1. 构造者,或
  2. 工厂方法

使用其中任何一个会有什么考虑因素?

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

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

与此相反,我可以在这些类上有一个静态工厂方法,如果找不到记录,它将返回null.

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

Dav*_*uda 187

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

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside
Run Code Online (Sandbox Code Playgroud)

到目前为止没有区别.现在想象我们有各种各样的学校类型,我们想从使用ElementarySchool转到HighSchool(来自ElementarySchool或实现与ElementarySchool相同的界面ISchool).代码更改将是:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside
Run Code Online (Sandbox Code Playgroud)

在接口的情况下,我们将:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside
Run Code Online (Sandbox Code Playgroud)

现在,如果你在多个地方都有这个代码,你可以看到使用工厂方法可能相当便宜,因为一旦你改变了工厂方法就完成了(如果我们使用带接口的第二个例子).

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

ISchool school = SchoolFactory.ConstructForStudent(myStudent);
Run Code Online (Sandbox Code Playgroud)

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

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

这样你就可以按照第四组 "程序到界面,而不是实现" 的第一个设计原则.

  • 这应该是公认的答案 (9认同)
  • 即使您认为它是一个简单的类,也有可能有人需要扩展您的简单类,因此工厂方法仍然更好。例如,您可以从 ElementarySchool 开始,但后来有人(包括您自己)可能会使用 PrivateElementarySchool 和 PublicElementarySchool 扩展它。 (3认同)
  • @David,很好的答案,但是您能否扩展一个示例,其中每个接口实现可能需要不同的构造参数。这是一个愚蠢的例子:`IFood 三明治 = new Sandwich(Cheese chz, Meat meat);` 和 `IFood 汤 = new Soup(肉汤,蔬菜蔬菜);` 工厂和或建筑商在这里如何提供帮助? (2认同)
  • 我刚刚阅读了其他三个关于工厂使用目的的解释,这是最终让我“点击”的一个。谢谢! (2认同)
  • 为什么这是不被接受的答案? (2认同)
  • 如果同时存在“ElementarySchool”和“HighSchool”,则此行将含糊不清:“ISschool school = SchoolFactory.Construct(); // 新的 ?里面` (2认同)

che*_*vim 69

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

静态工厂方法优点:

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

静态工厂方法的缺点:

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

  • 在我看来,这似乎是Java中的一个严重错误,然后是一般的OOD问题.有许多OO语言甚至没有*构造函数,但子类化工作正常. (4认同)

Gle*_*enn 65

来自Gamma,Helm,Johnson和Vlissides的" 设计模式:可重复使用的面向对象软件的元素"的第108页.

使用Factory Method模式时

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

  • 这没有解释我 (118认同)
  • 静态工厂方法不同于GoF设计模式 - 工厂方法模式.http://stackoverflow.com/questions/929021/what-are-static-factory-methods-in-java (19认同)
  • 这个答案不能回答问题,只是传递信息以阅读以理解/解释这个概念……更多是评论。 (2认同)

Fre*_*ool 26

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

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

  • 所以,(TDD风格)你会从构造函数开始,这是完成工作的最简单方法.一旦你开始得到代码气味(比如重复的条件逻辑确定要调用哪个构造函数),然后重构到工厂? (16认同)
  • 非常重要的一点。一项比较工厂和构造函数的用户研究发现非常重要的结果,表明工厂不利于 API 可用性:“用户使用工厂构造对象比使用构造函数需要更多的时间 (p=0.005)”[[API 中的工厂模式设计:可用性评估](https://www.cs.cmu.edu/~NatProg/papers/Ellis2007FactoryUsability.pdf)]。 (3认同)

Pat*_*ers 11

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

例如,工厂有可能进行缓存.

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


Eug*_*bun 11

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

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


blu*_*ote 10

除了"有效的java"(在另一个答案中提到),另一本经典书也暗示:

首选静态工厂方法(使用描述参数的名称)来重载构造函数.

例如.不要写

Complex complex = new Complex(23.0);
Run Code Online (Sandbox Code Playgroud)

而是写

Complex complex = Complex.fromRealNumber(23.0);
Run Code Online (Sandbox Code Playgroud)

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

  • 读本书的那部分带我到这里 (2认同)
  • 在相关说明中,您可能会发现一些关于命名的有用的[由 *java.time* 框架制定的命名约定](https://docs.oracle.com/javase/tutorial/datetime/overview/naming.html) `from...`、`to...`、`parse...`、`with...` 等等。请记住,*java.time* 类被构建为不可变的,但其中一些命名约定对于遵循可变类也可能很有用。 (2认同)

RS *_*ley 7

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

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

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


Lig*_*man 5

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

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

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

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

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

Microsoft的一些构造方法准则

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

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