【问题标题】:Static factory method vs public constructor静态工厂方法与公共构造函数
【发布时间】:2014-03-06 11:35:19
【问题描述】:

背景:

这是我目前正在处理的代码。首先是基类,它是一个帐户类,它保存有关帐户的信息,并具有一些在大多数情况下更改类属性值的方法。

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 参数、执行逻辑并且可以通过重载进行扩展的公共构造函数。我可以保留我的私有无参数构造函数以防止无参数构造并允许序列化。

我对编程很陌生。我想知道是否有特定原因使用静态工厂方法而不是公共构造函数,如上所述。做我想做的事情的首选方式是什么?

【问题讨论】:

    标签: c# constructor static-factory


    【解决方案1】:

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

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

    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 方法,您就可以对整个项目进行全局更改,交换数据库服务器甚至数据库提供程序,而无需在您实际使用数据库连接的所有地方更改您的代码。

    【讨论】:

    • 我真的很喜欢这个解释。我确实觉得在这种情况下使用命名构造函数更清楚,问题解决了!
    • @NickBull 谢谢,还请阅读 Ondrej 在他的回答中发布的.NET constructor guidelines。它们可能非常有用。
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      关于何时需要更喜欢静态工厂方法以及何时需要构造函数,我有一些考虑。

      1) 如果您需要执行一些构造函数无法完成的额外操作/初始化,请使用静态工厂方法。 例如,如果您需要发布新创建的 ActiveAccount 对象(例如 SomePublicList.Add(ActiveAccount); ),那么从它自己的构造函数中发布它是一种不好的做法。

      2) 考虑继承。如果您需要为ActiveAccount 创建一个后代类(例如NewActiveAccout),那么您需要在ActiveAccount 中提供一个非私有构造函数。在这种情况下,您可能需要更喜欢构造函数而不是工厂方法。

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

      【讨论】:

      • 关于#2,构造函数是否需要公开而不是受保护?在我看来,生成给定类型的一组方法应该独立于生成派生类的一组方法(Foo 允许每个人使用特定的构造函数来生成 Foo 实例这一事实并不意味着每个人应该允许从Foo 派生,并且构造函数可以被Foo 的派生使用这一事实并不意味着它应该可以用于创建Foo 类型的实例本身)。
      • @supercat 在我看来,如果一个类已经具有派生类所需的构造函数,那么在许多情况下,没有理由实现具有完全相同功能的静态工厂方法。此外,我认为在将对象构造为独立对象或作为派生类对象的一部分时,应统一考虑对象构造/初始化。在这两种情况下,都必须正确构造/初始化对象。通常构造函数范式完美地服务于这两种情况,作为对象构造/初始化的单点。
      • 如果String 没有公共构造函数,那么从String 派生一些类型可能是有意义的(例如AsciiStringUcs2StringFullUnicodeString 等。 ),所有这些都知道如何相互交互[因为绝大多数字符串只包含 0-127 的字符代码,将它们以打包格式存储可以节省大量空间和时间]。由于 .NET 不是这样设计的,因此编写了很多代码会破坏这种设计,但如果从一开始就理解 String 可能有很多表示......
      • ...它不会给客户端代码带来任何特别的困难。但是,任何公共构造函数的存在都使得无法将派生类型的实例化限制为定义 String 的程序集(一个 String 类,它允许两个不知道彼此存在的人)编写可以交互的String 的衍生物必须比每个子类型都有一组固定的必须使用的其他子类型复杂得多。
      猜你喜欢
      • 1970-01-01
      • 2011-05-04
      • 2014-02-18
      • 1970-01-01
      • 2013-07-24
      • 1970-01-01
      • 2011-03-01
      • 1970-01-01
      • 2014-07-13
      相关资源
      最近更新 更多