2009-06-12 3 views
1

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

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

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

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

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

+0

Если свойство дорогое, то оно работает вопреки рекомендациям MS относительно свойств против методов (http://msdn.microsoft.com/en-us/library/bzwdh01d(VS.71).aspx), но даже как метод у вас все же есть те же проблемы, конечно – annakata

ответ

1

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

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

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

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

+0

Звучит неплохо в целом. В коде, который побудил меня написать вопрос, 90% экземпляров создаются в таких местах, как «Потребитель 1», где я просто убираю уродство. Я делаю подсчет только в 10% случаев. Кроме того, одна из моих операций Calc() рекурсивно запрашивает более UglyData. Поэтому ваше решение означает, что я вообще не могу использовать свою дружественную оболочку, или у меня будет бесконечный цикл. –

+0

Пошел вперед и отметил это как ответ. Я фактически использовал опцию №3 в своем приложении. Но моя мотивация была принципиальной идеей Рида, что вы всегда должны быть действительными после их создания. Таким образом, мой недавно урезанный FriendlyWrapper и класс FriendlyWrapperWithExpensiveStuff, который наследует от него, делают все, что им нужно, в своих конструкторах. Чтобы повысить удобство использования, класс Expensive имеет перегруженный конструктор, который принимает любой существующий класс Friendly. –