【问题标题】:Design Pattern for implementing this type of lazy loading?实现这种延迟加载的设计模式?
【发布时间】:2013-11-01 20:13:28
【问题描述】:

我有许多类实现了这样的接口:

public interface IProcessor
{
    Foo Foo { get; set; }
    int Process();
}

我有一家生产这些产品的工厂,但我提前不知道会生产多少。问题是检索必要的Foo 对象的成本很高,并且在调用Process 之前,该属性实际上并未在IProcessor 中使用。

所以如果我的工厂有这样的方法:

public IProcessor CreateProcessor(int fooId)
{
    Foo foo = this.Context.GetFoo(fooId);

    return new ImplementedProcessor { Foo = foo };
}

我每次都得打电话给GetFoo。我真正想要的是这样的:

public IProcessor CreateProcessor(int fooId)
{
    this.CurrentlyTrackedFooIds.Add(fooId);

    return new ImplementedProcessor { Foo = what??? };
}

这个想法是直到ImplementedProcessor 需要Foo 来执行它的Process 方法,它将排队 fooId 以便可以像这样批量检索它们:

this.Context.GetAllFoosAtOnceCheaply(this.CurrentlyTrackedFooIds);

是否有任何延迟加载模式可以帮助解决此类问题?我可以完全控制整个项目,因此我不受现有IProcessorIProcessorFactory 接口的限制。我可以想办法做到这一点(可能是某种委托?),但它们看起来都不是很干净,我想通过在项目中拥有一百万个不同的实现来避免这种事情失控。任何帮助将不胜感激!

【问题讨论】:

    标签: c# design-patterns lazy-loading lazy-evaluation


    【解决方案1】:

    所以这里有两种类型可以让我们的生活超级变得轻松。第一个是Lazy。当你构造一个Lazy 对象时,你给它一个返回值的函数。然后,当您询问 Lazy 的值时,如果它没有启动,它会启动该函数,如果它没有完成则等待该函数,然后返回该值。它从不调用该函数两次。如果从不要求 Value ,它甚至不会触发该功能一次。

    然后我们可以创建一个ConcurrentDictionaryLazy 对象,这样我们就可以确保获得我们拥有的任何现有的Lazy 对象。请注意,它的GetOrAdd 方法明显是原子的。我们可以确定字典永远不会用另一个对象覆盖现有的Lazy 对象;它要么返回一个现有的,要么设置一个新的。这意味着我们永远不会在代表相同 ID 的两个不同 Lazy 对象上调用 Value

    毕竟,没有太多的代码:

    private  ConcurrentDictionary<int, Lazy<IProcessor>> lookup =
            new ConcurrentDictionary<int, Lazy<IProcessor>>();
    public IProcessor CreateProcessor(int fooId)
    {
        var lazy = lookup.GetOrAdd(fooId, new Lazy<IProcessor>(Context.GetFoo));
    
        return new ImplementedProcessor { Foo = lazy.Value };
    }
    

    【讨论】:

    • 谢谢,忘了Lazy!我真正的性能问题不在于多次检索相同的 fooId,而是同时检索多个 fooId 有显着的收益。将此标记为答案,因为它给了我一些好主意,但如果您对此有任何其他想法,我将不胜感激。再次感谢!
    • 嗯...回顾一下,在您的示例中使用Lazy 有什么意义?您是否不必将整个Lazy 对象传递给ImplementedProcessor,而不是lazy.Value,后者会立即执行它?
    • @Ocelot20 好吧,这里的目标是防止需要多次获取同一个对象;它实际上是一个缓存。
    • 对,但只是想知道Lazy 是否在这里做了什么特别的事情,因为ConcurrentDictionary&lt;int,Foo&gt; 可以做几乎相同的工作,因为lazy.Value 会立即被调用。
    • 不完全。 Lazy 为您提供的是,多次调用仍在计算值时只会导致进行一次计算。如果您不会处于需要某些东西而其他东西正在计算它的情况,或者这样做根本不是主要问题,那么是的,Lazy 不需要。
    【解决方案2】:

    从 .NET 4.0 开始,框架为此提供了一个 Lazy&lt;T&gt; 类:

    public interface IProcessor {
        Foo Foo { get; }
        int Process();
    }
    
    internal class ImplementedProcessor {
        internal Lazy<Foo> LazyFoo {get;set;}
        public Foo Foo {
            get {
                return LazyFoo.Value;
            }
        }
        public int Process() {
            ...
        }
    }
    
    public IProcessor CreateProcessor(int fooId) {
        return new ImplementedProcessor {
            LazyFoo = () => this.Context.GetFoo(fooId)
        };
    }
    

    但是,这不会批量检索Foos - 该过程将一个接一个地进行。只有当您的某些 ImplementedProcessor 项目永远不会调用 Process() 时,您才能节省一些时间。

    为此,您需要创建一个缓存,该缓存在检索到第一个 Foo 时延迟初始化,并留在那里等待剩余的 Foos 获取缓存值。不过,Lazy&lt;T&gt; 不太适合这个。考虑在调用Process 之前向IProcessor 添加一个方法以获取缓存所需的fooIds,并添加一个检索所有感兴趣的ID 的类。

    【讨论】:

      猜你喜欢
      • 2011-02-14
      • 1970-01-01
      • 2011-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多