Дизайн 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;
}
Какой здесь самый лучший маршрут?Варианты, которые я вижу:
- Изменяемый объект с явным методом Calculate(), как указано выше
- Изменяемый объект, где дорогостоящие вычисления выполняются в геттерах (и, вероятно, кэшируются)
- Разделить на два объекта, где один наследует (или, возможно, создает?) от другого
- Какой-то механизм статической + блокировки, как в вопросе C ++, приведенном выше
Я сам склоняюсь к № 2.Но на каждом маршруте есть потенциальные подводные камни.
Если вы выберете # 1 или # 2, то как бы вы четко и корректно реализовали цикл Consumer2 над изменяемыми параметрами?
Если вы выберете # 1 или # 3, как бы вы справились с будущими ситуациями, когда вы хотите вычислить только некоторые свойства, но не другие?Хотите создать N вспомогательных методов / производных классов?
Если вы выберете № 4, я думаю, что вы сумасшедший, но не стесняйтесь объяснить
Решение
В вашем случае, поскольку вы используете LINQ, вы собираетесь создавать эти объекты только в тех случаях, когда вам нужны вычисления.
Если это ваш стандартный шаблон использования, я бы просто поместил дорогостоящий расчет непосредственно в конструктор.Использование отложенной инициализации всегда выполняется медленнее, если только вы не планируете иметь некоторые случаи, когда вы не выполняете вычисления.Выполнение вычисления в геттерах ничего не спасет (по крайней мере, в данном конкретном случае).
Что касается изменчивости - изменяемые объекты со ссылочным синтаксисом и идентификатором (т.е.:классы в C #) действительно в порядке - это скорее проблема, когда вы имеете дело с изменяемыми типами значений (т.Е.:структуры).В .NET BCL существует множество изменяемых классов - и они не вызывают проблем.Проблема, как правило, возникает чаще одной, когда вы начинаете иметь дело с типами значений.Изменяемые типы значений приводят к очень неожиданному поведению.
В общем, я бы перевернул этот вопрос с ног на голову - как и где вы собираетесь использовать этот объект?Как вы можете сделать этот объект наиболее производительным (если он был определен как проблемный), не влияя на удобство использования?Все ваши варианты 1), 3) и 4) ухудшили бы удобство использования, поэтому я бы избегал их.В этом случае выполнение 2) не поможет.Я бы просто поместил его в конструктор, чтобы ваш объект всегда находился в допустимом состоянии (что очень хорошо для удобства использования и ремонтопригодности).