为什么要使用Create方法而不是直接使用"new"?

12

静态构造函数有哪些优势?何时使用它是恰当的?

public class MyClass
{
    protected MyClass()
    {
    }

    public static MyClass Create()
    {
        return new MyClass();
    }
}

然后通过创建类的实例来执行

MyClass myClass = MyClass.Create();

与仅使用公共构造函数创建对象相比,

MyClass myClass = new MyClass();

我可以看出,如果Create方法返回的是该类实现的接口的一个实例,那么第一种方法是有用的...它会强制调用者创建接口的实例而不是特定类型的实例。

17个回答

15

这是工厂模式,通常用于生产子类,但允许静态方法的参数决定生产哪一个。

如果不总是需要新对象,例如在池化实现中,也可以使用它。

如果所有对象实例都需要在创建时缓存或以其他方式注册,也可以使用它。这确保客户端不会忘记执行此操作。

当然,这也是单例模式的一部分。


1
我想补充一下Robert Paulson的回答,关于“不仅仅是简单的创建”,因为这似乎可以完善这个答案,或者至少扩展“缓存或其他已注册部分的答案”。 - Michael Prewecki

8

这不是一个单例模式...如果是的话,每次创建都会返回相同的实例。

这是一个工厂模式。通常我会使用接口而不是类来实现,所以我必须在这里做一些调整,但一个人为的例子如下:

public static MyClass Create()
{
    if(OS == Windows)
        return new MyDerivedClassForWindows();
    else if(OS == Linux)
        return new MyDerivedClassForLinux();
    etc....
}

你会返回MyClass还是IMyClass...假设MyClass不是一个接口但IMyClass是(我的意思是这只是惯例,但你明白我的意思)。 - Michael Prewecki
我会返回IMyClass。苹果的开发者们可能不想从MyClass派生(仅出于区别而这么做),但如果他们想在我的沙盒中玩耍,他们至少得实现IMyClass接口。 - ratchetr

4
你所发布的示例与使用普通构造函数没有任何优势。但是,有两种常见的模式可以使用此类方法来生成对象:
单例模式
当您想要防止创建多个对象实例,但仍想利用类的面向对象特性(例如字段、属性、无参数方法)时,可以使用单例模式。示例:
public class MySingletonClass
{
    private MySingletonClass currentInstance = null;

    public MySingletonClass CreateInstance()
    {
         if (currentInstance == null)
         {
              currentInstance = new MySingletonClass();
         }
         else
         {
              return currentInstance;
         }
    }
}

工厂模式

工厂模式是一种很好的将具体类的创建进行抽象的方法;例如,假设您有一些XML,并且根据在XML中看到的哪个节点(NodeA、NodeB或NodeC),您需要处理不同的类。例如:

public class MyXmlProcessorFactory
{
    public static IMyXmlProcessor GetMyXmlProcessor(XmlDocument document)
    {
         XmlNode rootNode = document.DocumentElement;

         if (rootNode.SelectSingleNode("NodeA") != null)
         {
              return new NodeAMyXmlProcessor();
         }
         else if (rootNode.SelectSingleNode("NodeB") != null)
         {
              return new NodeBMyXmlProcessor();
         }
         else if (rootNode.SelectSingleNode("NodeC") != null)
         {
              return new NodeCMyXmlProcessor();
         }
         else
         {
              return new DefaultMyXmlProcessor();
         }
    }
}

3

你说得对。这不是单例模式,而是工厂模式。

使用Create()而不是new可以为方法赋予有意义的名称,从而使用户更加清晰明了。

以此为例:

public class User
{
    public int id;
    public string userName;
    public string password;
    public string role;

    private User() { }

    public static User CreateByCredentials(string uid, string pwd)
    {
        User user = new User();
        user.userName = uid;
        user.password = pwd;
        return user;
    }

    public static User CreateById(int id)
    {
        User user = new User();
        user.id = id;
        return user;
    }

}

这种方法有两个优点:
  1. 我们可以返回一个空对象,而使用构造函数是不可能的(这在所有情况下可能或可能不有用)。
  2. 如果有很多不同的创建对象的方式,它给了你提供更有意义的函数名称的机会。例如,您可以使用User.CreateByCredentials(string username,string password)和User.CreateById(int id)。使用构造函数重载可以实现相同的功能,但通常没有同样的清晰度。

3

另一个原因,由Guid.NewGuid()静态方法证明,是如果类型是结构体。在这种情况下,CLR要求默认的无参数构造函数创建一个新实例,并将所有字段初始化为默认值。对于Guid来说,这意味着:

Guid g = new Guid();

将是Guid.Empty。显然,您希望能够创建一个随机的新Guid,因此Guid.NewGuid()方法就是一种实现方法。


Guid类在命名方面非常糟糕 - 不明显命名的NewGuid()是创建新(Guid)(随机)的唯一方法,而构造函数仅从现有数据创建Guid。您的答案观察到现实,但并未回答为什么这样做的问题。尽管Guid类中的命名不佳,但您需要思考Guid.NewGuid()与new Guid()有何不同。 - Robert Paulson

2
可能在多个类实例共享相同的初始化数据块的情况下会有用---这就是面向对象的工厂模式。
例如,连接到数据库的类--只需要一个连接,并且可以由类的所有实例共享。
参考链接:http://en.wikipedia.org/wiki/Factory_pattern Billy3

这不需要工厂方法。只需使用静态连接变量即可实现。 - Matthew Flaschen

2

1
这是通过使用私有构造函数,强制使MyClass为final的好方法。这样子类就不能被创建,因为它们的构造函数无法访问父类的构造函数。
另一个优点是当您只有一个构造函数参数时。然后您可以通过模拟命名参数来命名创建:
public static MyClass createWithDays(int days) {
...
}

现在将new MyClass(5)MyClass.createWithDays(5)进行比较。

1

这个模式通常在单例模式中使用。它非常有用,当您有一个类或类的门面应该只在程序的生命周期中实例化一次,通常是因为对象将使用资源。

C#中的操作方法


2
单例模式会返回对同一对象的引用,而工厂模式创建多个实例,这些实例可能共享数据。 - Billy ONeal
1
正如BillyONeal所提到的,这不是单例模式;单例模式强制只创建一个类的实例。使用单例模式的一个很好的例子是设置类,其中只应该存在一个类。 - Ed Altorfer
虽然你们两个都是正确的,他的代码并不一定传达了单例模式,但请记住他最初的问题围绕着工厂和单例使用的静态构造。 - Wayne Hartman

1

有很多原因你可能会这么做,例如始终从固定对象池(包括单例对象)返回实例,或者像你所说的那样,返回接口的实例。

但是如果你没有明确的理由去这么做,就不要这么做。


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