【问题标题】:C# design for an object where some properties are expensive: excuse to make it mutable?C# 为某些属性昂贵的对象设计:让它可变的借口?
【发布时间】:2009-06-12 15:52:27
【问题描述】:

是的,我知道,还有一个关于可变对象的问题。请参阅this 了解一般背景,this 了解与我的问题最接近的类似物。 (尽管它有一些不适用于此处的 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,我认为你疯了,但请随意解释

【问题讨论】:

标签: c# oop foreach mutable


【解决方案1】:

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

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

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

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

【讨论】:

  • 总体上听起来不错。然而,在促使我写下这个问题的代码中,90% 的实例都是在像 Consumer 1 这样的地方构建的,我只是在这些地方简单地包装了丑陋的东西。我只有 10% 的时间做任何计算。此外,我的 Calc() 操作之一递归地查询更多 UglyData。所以你的解决方案意味着我根本不能在那里使用我友好的包装器,否则我会有一个无限循环。
  • 继续并将其标记为答案。我实际上在我的应用程序中使用了选项#3。但我的动机是 Reed 的基本思想,即一旦构建,你就应该始终有效。因此,我新剥离的 FriendlyWrapper 和继承自它的 FriendlyWrapperWithExpensiveStuff 类都在其构造函数中完成了它们所需的一切。为了提高可用性,Expensive 类有一个重载的构造函数,它接受任何现有的 Friendly 类。
猜你喜欢
  • 1970-01-01
  • 2020-10-27
  • 1970-01-01
  • 2011-11-01
  • 2012-06-13
  • 2010-12-04
  • 1970-01-01
  • 2011-12-06
  • 2012-05-07
相关资源
最近更新 更多