静态工厂方法 VS 公共构造函数

11

背景:

这是我目前正在处理的代码。首先,基础类是一个账户类,它包含有关账户的信息,并具有某些方法,这些方法在很大程度上更改类属性的值。

public class Account {
    private string _username; [...]

    public string Username { get { return _username; } } [...]

    public Account() { }

    public Account(string[] args) { [...] }

    public virtual void ChangePassword(string newPassword) { [...] }
}

然后,我有另一个类用于已创建账户的情况,我将其命名为ActiveAccount。它包含了我想要在账户创建后才能执行的大部分操作逻辑。一些不需要包含在问题解释中的类,请使用您的想象力来假设这些类可能会做什么:

public class ActiveAccount : Account
{
    private List<Conversation> _conversations; [...]

    public List<Conversation> Conversations { get { return _conversations; } } [...]

    private ActiveAccount() { }

    public static ActiveAccount CreateAccount(Account account)
    {
        // Navigate to URL, input fields, create account, etc.
    }

    public override void ChangePassword(string newPassword)
    {
        // Navigate to URL, input fields, change password, etc.

        // Update property using base method, if no errors.
        base.ChangePassword(newPassword);
    }
}

我使用静态工厂方法有两个原因。1)我想要一个可定制和可扩展构建对象的方式(例如,将来我可能会有一个AccountTemplate,从中提供通用信息来创建账户;我可以很容易地创建另一个带有AccountTemplate参数的静态工厂方法重载),2)拥有一个无参构造函数使我更容易将这个对象序列化为XML / JSON。

问题:

然而,我意识到我完全可以有一个公共构造函数,接受一个Account参数,执行逻辑并可以像同样方便的扩展重载。我可以保留我的私有无参构造函数以防止无参构造并允许序列化。

我对编程相当陌生。我想知道是否有使用静态工厂方法而不是公共构造函数的特定原因,就像上面所解释的那样。以及我想做的事情的首选方式是什么?

3个回答

25

我不会称你所使用的是静态工厂。在我看来,它是一个“命名构造函数”,因为它位于类本身中,只是创建该特定类的对象。

它通常用于使操作更容易理解,例如比较:

int value = Int32.Parse(someString);
int value = new Int32(someString); // doesn't really exist

第一个版本明确表示它解析输入字符串,第二个版本则少得多。
更新:构造函数和像Int32.Parse这样的静态方法之间的一个重要区别是,静态方法可以选择在出现错误时返回null或抛出异常。构造函数只能抛出异常,或者——我不建议这样做——将对象留在某种半初始化状态下。
静态工厂用于解耦类,并使更改实现更容易,例如,您可以使用工厂方法返回接口来实例化数据库连接,而不是每次需要数据库连接时在代码中使用new运算符。
SqlConnection myConnection = new SqlConnection(connectionString);
IDbConnection myConnection = myFactory.CreateConnection();

优势在于只需简单更改CreateConnection方法,就可以对整个项目进行全局更改,替换数据库服务器甚至数据库提供商,而无需在实际使用数据库连接的所有地方更改您的代码。

我真的很喜欢这个解释。我觉得在这种情况下有一个命名构造函数更清晰,问题解决了! - Nick Bull
2
@NickBull 谢谢,还要阅读Ondrej在他的回答中发布的.NET构造函数指南。它们非常有用。 - Dirk

17
我建议您阅读.NET构造函数指南。在您的情况下,可能会有一些要点导致选择静态工厂而不是构造函数。即

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

如果所需操作的语义不能直接映射到新实例的构造,或者遵循构造函数设计指南感觉不自然,则考虑使用静态工厂方法代替构造函数。


0

关于何时需要使用静态工厂方法和构造函数,以下是我的考虑:

1)如果您需要执行一些无法通过构造函数完成的其他操作/初始化,则使用静态工厂方法。例如,如果您需要发布新创建的ActiveAccount对象(例如SomePublicList.Add(ActiveAccount);),则从其自身的构造函数中发布它是不好的实践。

2)考虑继承。如果您需要为ActiveAccount创建一个派生类(例如NewActiveAccout),则需要在ActiveAccount中提供一个非私有构造函数。在这种情况下,您可能需要优先选择构造函数而不是工厂方法。

3)如果您需要将一些参数传递给基类(Account)构造函数,则需要在ActiveAccount中实现一个构造函数,因为您无法从静态工厂方法中传递参数到基类构造函数。


关于问题 #2,构造函数需要是公有的而不是受保护的吗?在我看来,生成给定类型的方式集应该与生成派生方式集无关(Foo允许每个人使用特定的构造函数来生成Foo实例并不意味着每个人都应该被允许从Foo派生,一个构造函数可以被Foo的衍生物使用,但这并不意味着它可以用于创建类型Foo本身的实例)。 - supercat
在我看来,如果一个类已经有派生类需要的构造函数,那么在很多情况下就没有理由再实现一个完全具有相同功能的静态工厂方法。此外,我认为对象的构建/初始化应该在两种情况下统一考虑,即作为独立对象构建还是作为派生类对象的一部分构建。在这两种情况下,对象都必须被正确地构建/初始化。通常构造函数范例完美地服务于这两种情况,成为对象构建/初始化的单一点。 - Alex Antonov
如果String没有公共构造函数,那么从String派生出一些类型(例如AsciiStringUcs2StringFullUnicodeString等)可能是有意义的,它们都知道如何相互交互[由于绝大多数字符串仅包含字符代码0-127,将它们存储在紧凑格式中可以节省大量空间和时间]。由于.NET没有被设计成这样,因此编写了许多代码,如果采用这种设计方式,则会破坏这些代码,但如果从一开始就理解了String可以具有多种表示形式... - supercat
...这对客户端代码并不会带来任何特别的困难。然而,任何公共构造函数的存在都会使限制派生类型实例化为定义“String”的程序集所不可能,(一个允许两个人编写“String”派生类且可以相互交互的“String”类必须比每个子类型都有一个固定的其他子类型集合更复杂) 。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接