通过lambda factory与直接“new Type()”语法创建对象

时间:2013-10-09 03:39:04

标签: c# lambda factory-pattern

例如,考虑一个实用工具类SerializableList

public class SerializableList : List<ISerializable>
{
    public T Add<T>(T item) where T : ISerializable
    {
        base.Add(item);
        return item;
    }

    public T Add<T>(Func<T> factory) where T : ISerializable
    {
        var item = factory();
        base.Add(item);
        return item;
    }
}

通常我会这样使用它:

var serializableList = new SerializableList(); 
var item1 = serializableList.Add(new Class1());
var item2 = serializableList.Add(new Class2());

我也可以通过分解来使用它,如下所示:

var serializableList = new SerializableList(); 
var item1 = serializableList.Add(() => new Class1());
var item2 = serializableList.Add(() => new Class2());

第二种方法似乎是一种首选的使用模式,因为我最近注意到了SO。是真的如此(为什么,如果是的话)还是仅仅是品味问题?

4 个答案:

答案 0 :(得分:3)

举个例子,工厂方法很愚蠢。除非被调用者需要能够控制实例化点,实例化多个实例或延迟评估,否则它只是无用的开销。

编译器无法优化代理创建。

引用使用您在问题评论中提供的工厂语法的示例。这两个例子都试图(虽然很差)提供有保证的实例清理。

如果您考虑使用声明:

using (var x = new Something()) { }

天真的实施将是:

var x = new Something();
try 
{ 
}
finally
{
   if ((x != null) && (x is IDisposable))
     ((IDisposable)x).Dispose();
}

此代码的问题在于,在x分配之后,但在输入try块之前,可能会发生异常。如果发生这种情况,x将无法正确处理,因为finally块将不会执行。为了解决这个问题,using语句的代码实际上更像是:

Something x = null;
try 
{
   x = new Something();
}
finally
{
   if ((x != null) && (x is IDisposable))
      ((IDisposable)x).Dispose();
}

使用工厂参数引用的两个示例都试图处理同样的问题。传递工厂允许实例在受保护的块中实例化。直接传递实例可以避免在此过程中出现问题而不会调用Dispose()

在这些情况下,传递工厂参数是有道理的。

答案 1 :(得分:2)

<强>缓存

在你提供的例子中,没有其他人指出的那样有意义。相反,我会给你另一个例子,

public class MyClass{
    public MyClass(string file){
        // load a huge file
        // do lots of computing...
        // then store results...
    }
}

private ConcurrentDictionary<string,MyClass> Cache = new ....

public MyClass GetCachedItem(string key){
    return Cache.GetOrAdd(key, k => new MyClass(key));
}

在上面的例子中,假设我们正在加载一个大文件,我们正在计算一些东西,我们对该计算的最终结果感兴趣。为了加快访问速度,当我尝试通过Cache加载文件时,如果有缓存,Cache将返回缓存条目,只有当缓存找不到该项时,它才会调用Factory方法,并创建MyClass的新实例。

所以你要多次读取文件,但是你只创建只保存一次数据的类的实例。此模式仅用于缓存目的。

但是如果你没有缓存,并且每次迭代都需要调用new运算符,那么根本不使用工厂模式。

备用错误对象或错误记录

出于某种原因,如果创建失败,List可以创建错误对象,例如

 T defaultObject = ....

public T Add<T>(Func<T> factory) where T : ISerializable
{
    T item;
    try{
        item = factory();
    }catch(ex){
        Log(ex);
        item = defaultObject;
    }
    base.Add(item);
    return item;
}

在此示例中,您可以监视工厂是否在创建新对象时生成异常,并且当发生这种情况时,您记录错误,并返回其他内容并在列表中保留一些默认值。我不知道这将是什么实际用途,但错误记录在这里听起来更好。

答案 2 :(得分:1)

不,没有普遍偏好传递工厂而不是价值。但是,在非常特殊的情况下,更喜欢传递工厂方法而不是值。

想一想:

  

将参数作为值传递有什么区别,或者   将其作为工厂方法传递(例如使用Func<T>)?

答案很简单:执行顺序

  • 在第一种情况下,您需要传递值,因此您必须在调用目标方法之前获取它。
  • 在第二种情况下,您可以推迟创建/计算/获取的值,直到目标方法需要它为止。

为什么要推迟创建/计算/获取的价值?想到明显的事情:

  • 处理器密集型或内存密集型值的创建,只有在确实需要(按需)的情况下才会发生这种情况。这是延迟加载然后。
  • 如果值创建取决于目标方法可访问但不能从其外部访问的参数。因此,您将通过Func<T, T>而不是Func<T>

答案 3 :(得分:1)

该问题将方法与不同目的进行比较。第二个应命名为 CreateAnd 添加<T>(Func<T> factory)

因此,根据所需的功能,应使用一种或另一种方法。