我们可以没有构造函数吗?


25

假设出于某种原因,所有对象都是通过$ obj = CLASS :: getInstance()创建的。然后,我们使用setter注入依赖项,并使用$ obj-> initInstance();开始初始化。如果我们根本不使用构造函数,是否有无法解决的实际麻烦或情况?

ps用这种方法创建对象的原因是,我们可以根据一些规则替换getInstance()中的类。

我在用PHP工作,如果那件事


什么编程语言?
蚊蚋

2
PHP(为什么这么重要?)
Axel Foley 2014年

8
看起来您想要做的事情可以使用Factory模式来实现。
superM 2014年

1
就像一个注释:提到的工厂模式@superM是实现控制反转(依赖注入)的另一种方法。
Joachim Sauer 2014年

难道这不是javascript对对象的作用吗?
Thijser 2014年

Answers:


44

我想说,这严重地阻碍了您的设计空间。

构造函数是初始化和验证传入参数的好地方。如果您不能再使用它们了,则初始化,状态处理(或明确拒绝“断开”对象的构造函数)将变得更加困难,部分无法实现。

例如,如果每个Foo对象都需要a,Frobnicator那么它可能会在其构造函数中检入Frobnicator非null值。如果带走了构造函数,则检查起来会更加困难。您是否会检查使用地点的每一点?在一个init()方法中(有效地外部化构造方法)?永远不要检查并希望最好?

虽然你可能可能仍然实现一切(毕竟,你仍然图灵完备),有些东西会很多更难做。

就个人而言,我建议研究依赖注入 / 控制反转。这些技术还允许切换具体的实现类,但是它们不能防止编写/使用构造函数。


您是什么意思是“或完全否认“破碎”对象的构造函数”?
极客2014年

2
@Geek:构造函数可以检查其参数,并确定那些参数是否会导致工作对象(例如,如果您的对象需要a,HttpClient则它将检查该参数是否为非null)。如果不满足这些约束,则可能引发异常。使用构造和设置值方法实际上是不可能的。
Joachim Sauer 2014年

1
我认为OP只是在描述内部的构造函数的外部化init(),这是完全可能的,尽管这只会增加更多的维护负担。
彼得

26

构造函数的2个优点:

构造函数允许对象的构造步骤以原子方式完成。

我可以避免使用构造函数,而是对所有事物使用设置方法,但是如Joachim Sauer所建议的那样,强制属性又如何呢?对于构造函数,对象拥有自己的构造逻辑,以确保不存在此类的无效实例

如果创建的实例Foo需要设置3个属性,则构造函数可以引用所有3个属性,并对其进行验证,如果它们无效则抛出异常。

封装形式

通过仅依靠安装者,负担是对象的消费者要正确地构建它。有效的属性可能有不同的组合。

例如,每一个Foo实例都需要任一实例Bar作为属性bar或实例BarFinder作为属性barFinder。它可以使用任何一个。您可以为每个有效的参数集创建一个构造函数,并以此方式执行约定。

对象的逻辑和语义存在于对象本身内。这是很好的封装。


15

是的,您可以没有构造函数。

当然,您可能会得到很多重复的样板代码。而且,如果您的应用程序规模不限,当样板代码在整个应用程序中使用不一致时,您可能会花费大量时间来尝试查找问题的根源。

但是不,您不必严格“需要”自己的构造函数。当然,您也不严格“需要”类和对象。

现在,如果您的目标是使用某种工厂模式进行对象创建,那么在初始化对象时,这与使用构造函数并不相互排斥。


绝对。首先,甚至可以没有类和对象而生活。
JensG 2014年

5
所有人欢呼强大的汇编器!
DavorŽdralo2014年

9

使用构造函数的好处是使它们更容易确保您永远不会有无效的对象。

构造函数使您有机会将对象的所有成员变量设置为有效状态。然后,当您确保没有任何mutator方法可以将对象更改为无效状态时,您将永远不会有无效的对象,这将使您免于遇到许多错误。

但是,当在无效状态下创建新对象时,您必须调用一些设置器以使其可以进入可使用状态的有效状态,您可能会冒这样的风险,该类的使用者会忘记调用这些设置器或错误地调用它们,并且您最终得到一个无效的对象。

一种解决方法是仅通过工厂方法创建对象,该方法会在将其创建的每个对象都返回给调用者之前检查其有效性。


3

$ obj = CLASS :: getInstance()。然后,我们使用setter注入依赖项,并使用$ obj-> initInstance();开始初始化。

我认为您正在使这一过程变得更加困难。我们可以通过构造函数很好地注入依赖项-如果您有很多依赖项,则只需使用类似字典的结构即可指定要使用的依赖项:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

在构造函数中,您可以像这样确保一致性:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args 然后可以根据需要用于设置私有成员。

当像这样完全在构造函数中完成操作时,就不会有中间状态$obj存在但没有初始化,就像问题中描述的系统那样。最好避免这种中间状态,因为您不能保证总是正确使用该对象。


2

我实际上在考虑类似的事情。

我问的问题是“构造函数是做什么的,是否有可能做不同的事情?” 我得出了这些结论:

  • 它确保初始化某些属性。通过接受它们作为参数并进行设置。但这很容易由编译器实施。通过简单地将字段或属性注释为“必需”,编译器将在实例创建期间检查所有设置是否正确。创建实例的调用可能是相同的,只是不会有任何构造方法。

  • 确保属性有效。这可以通过断言条件轻松实现。同样,您只需使用正确的条件注释属性。一些语言已经做到了。

  • 一些更复杂的构造逻辑。现代模式不建议在构造函数中执行此操作,而是建议使用专门的工厂方法或类。因此,在这种情况下,构造函数的使用最少。

因此,请回答您的问题:是的,我相信这是可能的。但这需要对语言设计进行一些重大更改。

我只是注意到我的答案很不错。


2

是的,几乎不需要使用构造函数就可以做所有事情,但这显然浪费了面向对象编程语言的好处。

在现代语言中(我将在此处讨论我在其中编程的C#),您可以限制只能在构造函数中运行的部分代码。有了它,您可以避免笨拙的错误。这样的事情之一是只读修饰符:

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Joachim Sauer建议,而不是使用Factory设计模式阅读Dependency Injection。我建议阅读Mark Seemann撰写的.NET中的依赖注入


1

根据需求实例化对象的类型是绝对可能的。它可能是对象本身,使用系统的全局变量返回特定类型。

但是,在代码中有一个类可以是“全部”,这是动态类型的概念。我个人认为,这种方法会在您的代码中造成不一致,使测试变得复杂*,并且对于所提议的工作流程而言,“未来变得不确定”。

* 我指的是测试必须首先考虑类型,其次要达到的结果。然后,您将创建大型嵌套测试。


1

为了平衡其他一些答案,声称:

构造函数使您有机会将对象的所有成员变量设置为有效状态……您永远不会有无效的对象,这将使您免于遇到许多错误。

对于构造函数,对象拥有自己的构造逻辑,以确保不存在此类的无效实例。

这些陈述有时暗示着以下假设:

如果一个类具有一个构造函数,该构造函数在退出时已将对象置于有效状态,并且该类的任何方法都不会使该状态发生变化以使其无效,则该类之外的代码无法检测到对象该类的状态处于无效状态。

但这不是真的。大多数语言都没有禁止构造函数将thisself或其他语言称为)传递给外部代码的规则。这样的构造函数完全遵守上述规则,但是有将半构造对象暴露给外部代码的风险。这是次要的一点,但很容易被忽略。


0

这有点轶事,但我通常保留构造函数,以使对象完整和可用是必不可少的。除非有任何setter注入,否则构造函数一旦运行,我的对象就应该能够执行它所需的任务。

可以推迟的任何事情,我都不会考虑构造函数(准备输出值等)。通过这种方法,我觉得除了依赖注入之外,什么都不要使用构造函数。

这样做还有一个额外的好处,就是使您的思维设计过程很难进行布线,以免过早地做任何事情。您不会初始化或执行可能永远不会被使用的逻辑,因为充其量您所做的只是为下一步工作做的基本设置。

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.