4

例如,考虑一个实用程序类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

4 回答 4

3

鉴于您的示例,工厂方法很愚蠢。除非被调用者需要控制实例化点、实例化多个实例或惰性求值的能力,否则它只是无用的开销。

编译器将无法优化委托创建。

参考您在问题评论中给出的使用工厂语法的示例。这两个示例都在尝试(尽管效果不佳)来保证对实例的清理。

如果您考虑使用 using 语句:

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()调用。

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

于 2013-10-13T06:40:01.513 回答
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));
}

在上面的例子中,假设我们正在加载一个大文件并且我们正在计算一些东西并且我们对该计算的最终结果感兴趣。为了加快访问速度,当我尝试通过缓存加载文件时,如果缓存有缓存条目,缓存将返回给我,只有当缓存没有找到该项目时,它才会调用工厂方法,并创建新的 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;
}

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

于 2013-10-16T10:27:08.797 回答
1

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

想一想:

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

答案很简单:执行顺序

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

为什么要推迟价值创造/计算/获取?显而易见的事情浮现在脑海:

  • 处理器密集型或内存密集型值的创建,您希望仅在确实需要该值的情况下发生(按需)。这就是延迟加载
  • 如果价值创造取决于目标方法可以访问但不能从外部访问的参数。所以,你会通过Func<T, T>而不是Func<T>.
于 2013-10-16T19:44:39.597 回答
1

该问题比较了具有不同目的的方法。第二个应该命名为CreateAnd Add <T>(Func<T> factory)

所以根据需要什么功能,应该使用一种或另一种方法。

于 2016-05-06T10:40:10.930 回答