工厂与实例构造函数

时间:2010-06-02 17:06:16

标签: c# oop coding-style constructor

我想不出为什么一个比另一个更好的原因。比较这两个实现:

public class MyClass
{
    public MyClass(string fileName)
    {
        // some code...
    }
}

而不是:

public class MyClass
{
    private MyClass(){}

    public static MyClass Create(string fileName)
    {
       // some code...
    }
}

.Net框架中有一些地方使用静态方法来创建实例。起初我在思考,它注册它的实例来跟踪它们,但是常规构造函数可以通过使用私有静态变量来做同样的事情。

这种风格背后的原因是什么?

10 个答案:

答案 0 :(得分:49)

注意:你拥有的是不是静态构造函数,它是一个静态函数,它创建实例而不是自己调用实例构造函数。静态构造函数完全是另一回事。

工厂模式是使用函数(静态或非静态)实例化类型而不是直接使用构造函数的典型示例。请注意,实际的实例构造函数无论如何都会被调用,但静态函数提供了一个间接层,允许它返回任何类型的实例,该实例是或者从返回类型继承,而不是只返回的实例。是返回类型。

例如:

public abstract class BaseClass
{
    public static BaseClass Create(int parameter)
    {
        if (parameter == 1)
        {
            return new Class1();
        }
        else
        {
            return new Class2();
        }
    }
}

internal class Class1 : BaseClass
{
    //code here ...
}

internal class Class2 : BaseClass
{
    //code here ...
}

这允许您隐藏Class1Class2来自外部程序集,同时仍允许消费者处理专门的事情。

答案 1 :(得分:19)

我正在研究这个问题并遇到了这个问题,但并不觉得它已经得到了充分的回答。但是,我确实找到了这篇方便的文章 - Design Guidelines Update: Factories vs. Constructors - 来自Krzysztof Cwalina(.NET Framework的首席架构师)。值得一读整篇文章,但这里有一些要点的简要概述:

  

创建类型实例的最常见且一致的方法是   通过它的构造函数。但是,有时候一个更好的选择是   使用工厂模式。

     

比工厂更喜欢施工人员,因为他们通常更多   与专业建筑机制一致和方便。

     

将工厂操作实现为方法,而不是属性。

     

将实例作为方法返回值返回,而不是作为输出参数。

     

如果您需要更多控制创建,请考虑使用工厂   实例模式。

     

考虑通过连接“Create”和名称来命名Factory方法   正在创建的类型。

     

考虑通过连接类型名称来命名工厂类型   创建和“工厂。”

     

如果是,请不要使用静态方法实现工厂   构造操作必须可供子类专门化。

     

使用Factory进行转换样式操作。

     

如果操作需要参数信息,请使用工厂   传递给构造函数感觉不自然。

     

在创建操作的情况下,请勿使用Factory   用作实例化类型的核心场景。在这些   情况,可发现性和一致性是至关重要的。

     

考虑使用构造函数而不是Factory。只有在此之后   思考过程你应该继续执行一个   工厂。

     

工厂通常也可以方便地实现类型专业化   多态性。

     

如果API用户将编码到基类或,则使用工厂   接口的实现可能随时间而变化。

     

在开发人员可能不知道哪种情况下,请使用Factory方法   要构造的类型,例如在对基本类型进行编码时   接口。

     

将工厂操作实现为虚拟实例方法   如果它们必须支持多态扩展,则为静态。

     

对于基于实例的工厂,请使用Singleton模式   不要强迫开发人员实例化Factory类型   调用其中一个成员。

     

如果构造函数不足,请使用Factory方法   描述正在执行的操作和附加信息   从一个单独命名的工厂获得了一个操作的目的   更加清晰。

答案 2 :(得分:10)

工厂方法帮助的另一种常见情况(如C#which don't support type inference from constructor之类的语言)是必须最小化类型参数规范的情况。考虑Tuple类的常见情况。你可以这样做:

new Tuple<int, int>(1, 1); //constructor call

Tuple.Create(1, 1); //factory pattern.

答案 3 :(得分:7)

静态Create方法可以实例化并返回:

  • MyClass个实例
  • MyClass
  • 的任何子类的实例
  • null

答案 4 :(得分:3)

工厂方法通常用于隐藏正在创建的对象的确切类型。抽象类可以有一个静态工厂方法,然后根据某些条件选择具体的派生类来实例化。只有抽象基类是公开记录的。这使得库供应商可以自由地更改实例化的确切类型,而不会破坏现有代码。

.NET框架中的一个示例是Expression类上的静态工厂方法。

答案 5 :(得分:1)

有时,对所创建的类的每个实例进行簿记和/或资源管理非常重要。因此,重要的是每个结构都要在全球范围内进行管理,并且采用静态方法进行构建将很好地完成。

答案 6 :(得分:1)

正如您所看到的,myClass并未遵循“经典”工厂模式(实例的类在工厂外未知/暴露)。

但是在这种情况下,.NET框架团队可能没有针对工厂模式,但我想他们不希望您通过构造函数直接用文件名新建目标类。 如果只提供此类,工厂可能会过度。

这种模式有时可以在clone()方法中看到,其中一个对象可以返回一个实例,它是一种自身的副本。

也许虽然该类是公共的,但他们可能想要对实例化,文件名等进行一些检查,如果他们实现了工厂,仍然可以创建并调用目标类,绕过检查。

答案 7 :(得分:1)

为您构建的方法对于:

非常有用
public class Class
    {
        public static List<Class> FromFiles(IEnumerable<String> paths)
        {
            List<Class> instances = new List<Class>();
            foreach (string path in paths)
            {
                instances.Add(new Class() { Path = path });
            }
            return instances;
        }

        public string Path { get; set; }
    }

...因为“普通”构造函数不能这样做。

答案 8 :(得分:1)

不会将依赖关系传递给引用程序集的程序集是另一个原因。

似乎在另一个程序集中调用类的构造函数时,您的程序集将需要引用定义任何重载所使用的任何类型的所有程序集。您可以使用不同命名的工厂方法来解决此强制依赖。

例如:

Assembly1.dll(需要引用BarAssembly.dll不那么明显)

class Class1 { 
    void Main(){
        var foo = new Foo();
    }
}

Assembly2.dll(此处不需要引用BarAssembly.dll,因为CreateFoo和CreateFooWithBar不重载)

class Class2 { 
    void Main(){
        var foo = CreateFoo();
    }
}

FooAssembly.dll(需要引用明显的BarAssembly.dll)

class Foo { 
    public CreateFoo(){
        ...
    }
    public CreateFooWithBar(Bar bar){
        ...
    }
    public Foo(){
        ...
    }
    public Foo(Bar bar){
        ...
    }
}

BarAssembly.dll

class Bar { 
    public Bar(){
        ...
    }
}

注意:在VS2013上针对.NET Framework 4.5进行构建

答案 9 :(得分:0)

你可以讨论工厂方法。尝试curry一个构造函数为您提供:工厂方法。

我在我的一些枚举Windows设备管理器树的代码中使用它。其中一个类列出了串口,每个串口都有一个连接工厂属性,该属性返回一个curried工厂方法,其中存储内部状态的端口地址(真正丑陋的PNP设备字符串),同时提供波特率,停止位,奇偶校验后面。