你什么时候会选择使用工厂而不是构造函数来创建对象?

11

你在什么情况下会使用工厂方法而不是构造函数来创建对象?

  1. 始终使用工厂方法。
  2. 只有在存在除检查null之外的不变式检查时才使用工厂方法。
  3. 始终使用构造函数。
  4. 很少使用工厂方法...那些情况是什么?

优缺点

更新:我正在我的项目中应用领域驱动设计中的工厂模式。创建工厂的原因之一是减少领域模型中的噪音。

谢谢

9个回答

13

如果我有一个抽象基类(或接口)和几个具体派生类,并且存在某种逻辑来创建其中一个具体类,则我会使用工厂模式。 我在工厂中实现该逻辑。


3
工厂模式的核心目的是将对象的创建与客户端分离,因此,在工厂模式中具体类和抽象类之间保持1:1的比例并没有什么问题。 - devlord
1
@lorddev 是的,但是YAGNI。当你真正需要它时,你可以使用工厂模式。 - inf3rno

5

当实现某个接口的具体类需要在运行时选择时(例如从配置文件中选择),使用工厂是最明显的情况。我并不经常使用工厂,但是当我希望两个对象高度解耦时,我更倾向于使用工厂来获取其中一个对象的实例。


3

关于与这个主题相关的C#有趣的事情是,类定义中指定的泛型类型上的new()约束强制由泛型容器类型处理的类型实现一个无参数构造函数。只有在想要在类内创建T类型的实例(如GenericType<T>)时才需要new()约束。我认为这明确支持类工厂,特别是生成泛型类型的工厂。

为了颠覆这个要求,Windows Communication Foundation (WCF)有一个ChannelFactory类,定义了以下静态工厂方法:

public static TChannel CreateChannel(Binding binding, EndpointAddress endpointAddress, Uri via)
{
    ChannelFactory<TChannel> factory = new ChannelFactory<TChannel>(binding);
    if (factory.HasDuplexOperations())
    {
        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString("SFxInvalidStaticOverloadCalledForDuplexChannelFactory1", new object[] { factory.channelType.Name })));
    }
    TChannel channel = factory.CreateChannel(endpointAddress, via);
    ChannelFactory<TChannel>.SetFactoryToAutoClose(channel);
    return channel;
}

如果你在Reflector中查看类反汇编(System.ServiceModel程序集和System.ServiceModel.Channels命名空间),你会注意到“new()”没有被用作约束条件。

这是因为CreateChannel方法使用typeof(TChannel)将对象创建委托给链条下方...

public virtual TChannel CreateChannel(EndpointAddress address, Uri via)
{
    TChannel local;
    bool traceOpenAndClose = base.TraceOpenAndClose;
    try
    {
        using (ServiceModelActivity activity = (DiagnosticUtility.ShouldUseActivity && base.TraceOpenAndClose) ? ServiceModelActivity.CreateBoundedActivity() : null)
        {
            if (DiagnosticUtility.ShouldUseActivity)
            {
                ServiceModelActivity.Start(activity, this.OpenActivityName, this.OpenActivityType);
                base.TraceOpenAndClose = false;
            }
            if (address == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("address");
            }
            if (base.HasDuplexOperations())
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString("SFxCreateNonDuplexChannel1", new object[] { base.Endpoint.Contract.Name })));
            }
            base.EnsureOpened();
            local = (TChannel) this.ServiceChannelFactory.CreateChannel(typeof(TChannel), address, via);
        }
    }
    finally
    {
        base.TraceOpenAndClose = traceOpenAndClose;
    }
    return local;
}

当Type类被传递下去并最终调用以下方法时,您可以跟随委托链更深几层:

RemotingServices.CreateTransparentProxy(this, classToProxy, stub, stubData);

这是一个非常复杂的工厂,但它是我见过最复杂的工厂。有趣的是,所有的机械构造最终都会通过WCF从System.Runtime.Remoting.Proxies命名空间创建一个RealProxy类。

总之,工厂适用于具有很多复杂性或需要受益于动态类型构建的对象。


2
我不确定您是如何选择阈值的...
如果您不想将对象的使用者与构建过程分离,则工厂模式是合适的。以下情况可能涉及到这种模式:
- 您可能希望在运行时替换实现。通常,这与使用接口而不是具体类相结合。Java 中的一个例子是如何从 DocumentBuilder 中获取 Document 对象。 - 您可能希望限制对象实例的数量。考虑构造具有有限线程对象数量的池,而不是一直创建新的线程对象。
请查看《四人组设计模式》(Gamma 等)的相关内容,详细了解工厂模式的使用情况。

1

我喜欢保持构造函数的数量不太多;超过两到三个,我会质疑对象的构建是否被设计良好。

在需要引入额外的构造函数以支持设置各种可选属性的情况下,我喜欢使用Builder,如在Effective Java(Joshua Bloch,第2版)中所述。


1

这里有一个激进的想法(我并不是真正提倡它,但我认为它不会有害):

始终使用工厂方法!

工厂方法更加灵活,例如,它们可以缓存结果或返回子类。

因此,不要这样写:

class SomeClass {
  public SomeClass(/*parameters*/) { /*...*/ }
} 

始终使用:

class SomeClass {
  protected SomeClass(/*parameters*/) { /*...*/ }
  public static SomeClass New(/*parameters*/) {
    return new SomeClass(/*parameters*/);
  }
} 

调用者代码从以下内容更改:

SomeClass sc = new SomeClass();

致:

SomeClass sc = SomeClass.New();

现在,您可以更改“构造函数”逻辑以返回子类或缓存实例,而不会影响所有调用者。您现在控制“构造函数”的返回值。


0

我试图在它们之间进行衡量。我认为当以下情况时,您应该使用工厂模式:

  1. 参数数量很多。
  2. 参数是可选的。(两者都使用内部类 Builder 模式)
  3. 您需要更改参数顺序以执行其他操作,因为参数类型相同但数据不同。
  4. 您需要一个单例类(最好使用枚举)。

在这种情况下,使用工厂模式,您可以为返回的对象状态提供适当的名称。


0
当实例化具体类的决定不由客户端决定时,请使用工厂。例如,在存在多个对象“家族”并且选择使用哪个家族是在其他地方进行的情况下。

0

我认为你把生成器模式和工厂模式混淆了。我建议只使用构造函数并完成它。听起来(没有看到代码)你可能有点过于深思熟虑或者过度分析代码。


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