1

是的,我知道,关于可变对象的另一个问题。有关一般背景的信息,参阅内容,与我的问题最接近的类似内容请参见此内容。(尽管它有一些不适用于此处的 C++ 特定泛音)

让我们假设下面的伪代码代表了最好的界面设计。也就是说,它是业务语义(就目前而言)到 OO 类型的最清晰表达。自然地,UglyData 和我们使用它来做的事情会发生增量变化。

public class FriendlyWrapper
{
    public FriendlyWrapper(UglyDatum u)
    {
        Foo = u.asdf[0].f[0].o.o;
        Bar = u.barbarbar.ToDooDad();
        Baz = u.uglyNameForBaz;
        // etc
    }

    public Widget Foo { get; private set; }
    public DooDad Bar { get; private set; }
    public DooDad Baz { get; private set; }
    // etc
    public WhizBang Expensive1 { get; private set; }
    public WhizBang Expensive2 { get; private set; }

    public void Calculate()
    {
        Expensive1 = Calc(Foo, Bar);
        Expensive2 = Calc(Foo, Baz);
    }

    private WhizBang Calc(Widget a, DooDad b) { /* stuff */ }

    public override void ToString()
    {
        return string.Format("{0}{1}{2}{3}{4}", Foo, Bar, Baz, Expensive1 ?? "", Expensive2 ?? "");                             
    }
}

// Consumer 1 is happy to work with just the basic wrapped properties
public string Summarize()
{
    var myStuff = from u in data
                  where IsWhatIWant(u)
                  select new FriendlyWrapper(u);

    var sb = new StringBuilder();
    foreach (var s in myStuff)
    {
        sb.AppendLine(s.ToString());
    }
    return sb.ToString();
}

// Consumer 2's job is to take the performance hit up front.  His callers might do things 
// with expensive properties (eg bind one to a UI element) that should not take noticeable time. 
public IEnumerable<FriendlyWrapper> FetchAllData(Predicate<UglyDatum> pred)
{
    var myStuff = from u in data
                  where pred(u)
                  select new FriendlyWrapper(u);

    foreach (var s in myStuff)
    {
        s.Calculate();  // as written, this doesn't do what you intend...
    }

    return myStuff;
}

这里最好的路线是什么?我可以看到的选项:

  1. 具有显式 Calculate() 方法的可变对象,如上
  2. 在 getter 中完成昂贵计算的可变对象(并且可能被缓存)
  3. 拆分为两个对象,其中一个从另一个继承(或者可能是组合?)
  4. 某种静态+锁定机制,如上面链接的 C++ 问题

我自己倾向于#2。但每条路线都有潜在的陷阱。

如果您选择 #1 或 #2,那么您将如何以清晰、正确的方式实现 Consumer2 的可变循环?

如果您选择#1 或#3,您将如何处理将来只想计算某些属性而不计算其他属性的情况?愿意创建 N 个辅助方法/派生类吗?

如果您选择#4,我认为您很疯狂,但请随时解释

4

1 回答 1

1

在您的情况下,由于您使用的是 LINQ,因此您只会在需要计算的情况下构建这些对象。

如果这是您的标准使用模式,我会将昂贵的计算直接放在构造函数中。除非您计划在某些情况下不计算,否则使用延迟初始化总是较慢。在 getter 中进行计算不会保存任何东西(至少在这种特定情况下)。

至于可变性 - 具有引用语法和标识的可变对象(即:C# 中的类)真的没问题 - 当您处理可变值类型(即:结构)时,这更成问题。.NET BCL 中有很多很多可变类 - 它们不会引起问题。当您开始处理值类型时,问题通常更多。可变值类型会导致非常意外的行为。

一般来说,我会把这个问题颠倒过来——你将如何以及在哪里使用这个对象?如何在不影响可用性的情况下使该对象具有最高性能(如果确定它有问题)?您的 1)、3) 和 4) 选项都会影响可用性,所以我会避免使用它们。在这种情况下,执行 2) 将无济于事。我只是将它放在构造函数中,因此您的对象始终处于有效状态(这对可用性和可维护性非常有利)。

于 2009-06-12T15:58:02.443 回答