C # дизайн для объекта, где некоторые свойства дороги: извините, чтобы сделать его изменчивым? - PullRequest
1 голос
/ 12 июня 2009

Да, я знаю, еще один вопрос об изменчивых объектах. См. this для общего фона и this для ближайшего аналога моего вопроса. (хотя он имеет некоторые специфические для C ++ обертоны, которые здесь не применяются)

Предположим, что следующий псевдокод представляет лучший интерфейс . То есть, это наиболее ясное выражение бизнес-семантики (как они сегодня стоят) в типе ОО. Естественно, 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. Изменчивый объект, где в геттере выполняются дорогостоящие вычисления (и, вероятно, кешируется)
  3. Разделить на два объекта, где один наследует (или, возможно, составляет?) От другого
  4. Какой-то статический + блокирующий механизм, как в вопросе C ++, связанном выше

Я сам склоняюсь к # 2. Но у каждого маршрута есть потенциальные подводные камни.

Если вы выберете # 1 или # 2, то как бы вы реализовали цикл Consumer2 поверх изменяемых таблиц ясным и правильным способом?

Если вы выберете № 1 или № 3, как бы вы справились с будущими ситуациями, когда вы хотите рассчитать только некоторые свойства, но не другие? Желаете создать N вспомогательных методов / производных классов?

Если вы выберете # 4, я думаю, что вы сумасшедший, но не стесняйтесь объяснять

1 Ответ

1 голос
/ 12 июня 2009

В вашем случае, поскольку вы используете LINQ, вы будете строить эти объекты только в тех случаях, когда вы хотите вычислить.

Если это ваш стандартный шаблон использования, я бы просто поместил дорогой расчет прямо в конструктор. Использование отложенной инициализации всегда происходит медленнее, если только вы не планируете иметь случаи, когда вы не рассчитываете. Выполнение расчета в геттерах ничего не спасет (по крайней мере, в данном конкретном случае).

Что касается изменчивости - изменяемые объекты со ссылочным синтаксисом и идентичностью (то есть: классы в C #) действительно хороши - это больше проблема, когда вы имеете дело с изменяемыми типами значений (то есть: структурами). В .NET BCL есть много, много изменяемых классов, и они не вызывают проблем. Проблема, как правило, больше, когда вы начинаете работать с типами значений. Изменчивые типы значений приводят к очень неожиданному поведению.

В общем, я бы перевернул этот вопрос с ног на голову - Как и где вы собираетесь использовать этот объект? Как вы можете сделать этот объект наиболее эффективным (если он был определен как проблемный), не влияя на удобство использования? Ваши варианты 1), 3) и 4) могут ухудшить удобство использования, поэтому я бы их избегал. В этом случае выполнение 2) не поможет. Я бы просто поместил его в конструктор, чтобы ваш объект всегда находился в допустимом состоянии (что очень хорошо для удобства использования и обслуживания).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...