【问题标题】:Constructors vs Factory Methods [closed]构造函数与工厂方法[关闭]
【发布时间】:2010-10-12 08:27:33
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

    标签: oop ooad


    【解决方案1】:

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

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

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

    这样你就遵循gang of four book“程序到接口,而不是实现”的第一个设计原则。

    【讨论】:

    • 即使你认为它是一个简单的类,也有可能有人需要扩展你的简单类,所以工厂方法仍然更好。例如。你可以从 ElementarySchool 开始,但后来有人(包括你自己)可以用 PrivateElementarySchool 和 PublicElementarySchool 扩展它。
    • 这应该是公认的答案
    • @David,很好的答案,但是您能否扩展一个示例,其中每个接口实现可能需要不同的构造参数。这是一个愚蠢的例子:IFood sandwich = new Sandwich(Cheese chz, Meat meat);IFood soup = new Soup(Broth broth, Vegetable veg); 工厂和/或建造者如何在这里提供帮助?
    • 我刚刚阅读了其他三个关于工厂使用目的的解释,这就是我最终“点击”的那个。谢谢!
    • 为什么这是不被接受的答案?
    【解决方案2】:

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

    静态工厂方法的优点:

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

    静态工厂方法的缺点:

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

    【讨论】:

    • 在我看来,这似乎是 Java 中的一个严重错误,然后是一般的 OOD 问题。有许多 OO 语言甚至没有 构造函数,但子类化工作得很好。
    • @cherouvim 为什么如果( factory methods are better than Constructors. ( Item-1 ) ) Effective java大部分代码是使用构造函数编写的
    • 好点。虽然它是 Java 特定的。可以为使工厂方法与其他静态方法区分开来的语言特性创建一个案例。
    【解决方案3】:

    来自Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides.的第108页

    使用工厂方法模式

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

    【讨论】:

    • 静态工厂方法不同于 GoF 设计模式 - 工厂方法模式。 stackoverflow.com/questions/929021/…
    • 这对我没有任何解释
    • 这个答案没有回答问题,只是传递信息以阅读以理解/解释这个概念......这更像是一个评论。
    • 这 3 个要点是 AND 还是 OR 条件?
    • 另一个选择工厂方法而不是构造方法的原因是提供一个描述性的方法名称。如果单独的参数类型/名称不能传达实例化背后的逻辑,这会很有帮助。
    【解决方案4】:

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

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

    【讨论】:

    • 因此,(TDD 风格)您将从构造函数开始,作为完成工作的最简单方法。一旦你开始产生代码异味(比如重复的条件逻辑确定要调用哪个构造函数),然后重构为工厂?
    • 非常重要的一点。一项比较工厂和构造函数的用户研究发现了非常显着的结果,表明工厂不利于 API 可用性:“用户使用工厂构造对象比使用构造函数需要更多的时间 (p=0.005)”[The Factory Pattern in API Design: A Usability Evaluation]。跨度>
    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

        除了“有效java”(在另一个答案中提到),another classic book还建议:

        优先使用静态工厂方法(具有描述参数的名称)而不是重载的构造函数。

        例如。不要写

        Complex complex = new Complex(23.0);
        

        而是写

        Complex complex = Complex.fromRealNumber(23.0);
        

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

        【讨论】:

        • 阅读这本书的那部分让我来到这里
        • @Bayrem:我也是,我最近在重读它,并认为我应该将它添加到答案中。
        • 在相关说明中,您可能会发现一些naming conventions worked out by the java.time frameworkfrom…to…parse…with… 等的命名很有帮助。请记住,java.time 类被构建为不可变的,但其中一些命名约定对于可变类也可能有用。
        • 这并没有说明原因(也许这本书有?)
        【解决方案8】:

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

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

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

        【讨论】:

          【解决方案9】:

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

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

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

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

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

          Some constructor guide lines from Microsoft:

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

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

          【讨论】:

            【解决方案10】:

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

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

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-01-31
              • 1970-01-01
              • 1970-01-01
              • 2014-02-18
              • 1970-01-01
              • 2016-11-04
              • 1970-01-01
              • 2013-07-24
              相关资源
              最近更新 更多