Здравствуйте, stump, Вы писали:
IB>>Меньше связность, больше инкапсуляция. S>Один класс — ноль связей.
Как это ноль? Про связи внутри класса ты уже забыл? Зря, они как раз самые страшные.
S> Нет никакой нужды защищать метод foo() от изменений в классе, когда этот метод принадлежит самому классу.
Обосновать можешь?
S>Нет, критерии тут тоже четкие. Функция работает с состоянием класса значит это функция класса
Обосновать можешь?
S>, плюс принцип single responsibility.
SRP тут вообще не в тему.
S>Вамое главное что мои критерии причинные, а твои хоть и четкие но причины ты для них сформклировать не можешь.
Причину тебе уже неоднократно называли. Могу повторить — как и у подавляющего большинства архитектурных правил, это уменьшение связности кода.
... << RSDN@Home 1.2.0 alpha 4 rev. 1095 on Windows Vista 6.0.6001.65536>>
Здравствуйте, stump, Вы писали:
S>Я, кстати, тоже во всю использую "анемичную модель". Но тут немного другой случай, ближе к грунту, так сказать.
По мне, использование Poor domain позволяет увеличить возможность повторного использования, как алгоритмов работы над доменом, так и самих доменных объектов.
Lloyd пишет: > Это в основном программеры уверены, что пользы от него ноль. А те, кто > использует, придерживается иного мнения.
А я и не программер нынче. Только толку от него как было ноль, так и
осталось. Я бы даже сказал, минус. Иногда смотришь на код работнечгов и
думаешь: нафига нужно было вот это размазывание манной каши по тарелке...
Posted via RSDN NNTP Server 2.1 beta
Всё, что нас не убивает, ещё горько об этом пожалеет.
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, stump, Вы писали:
IB>>>Меньше связность, больше инкапсуляция. S>>Один класс — ноль связей.
AVK>Как это ноль? Про связи внутри класса ты уже забыл? Зря, они как раз самые страшные.
В software design под связностью (coupling) понимают связи между структурными единицами (классами, модулями).
Классы как раз и создаются для того чтобы инкапсулировать семантически родственные связи. Чтобы они не были очень страшными, сле дует придерживаться определенный принципов проектирования (SRP к примеру).
S>> Нет никакой нужды защищать метод foo() от изменений в классе, когда этот метод принадлежит самому классу.
AVK>Обосновать можешь?
Могу. Принцип черного ящика, метод инкапсулирован в класс, не имеет значения, что и как происходит внутри, важен только интерфейс.
S>>Нет, критерии тут тоже четкие. Функция работает с состоянием класса значит это функция класса
AVK>Обосновать можешь?
Собственно целый день этим и занимаюсь. Имеющий глаза увидит.
S>>Вамое главное что мои критерии причинные, а твои хоть и четкие но причины ты для них сформклировать не можешь.
AVK>Причину тебе уже неоднократно называли. Могу повторить — как и у подавляющего большинства архитектурных правил, это уменьшение связности кода.
Вместо одного класса два. Уменьшили, блин.
Здравствуйте, stump, Вы писали:
S>В software design под связностью (coupling) понимают связи между структурными единицами (классами, модулями).
Хочешь поспорить о терминах? Ну пусть это будет не coupling, назови как хочешь. Главное — связи внутри класса еще страшнее связей между классами.
S>Классы как раз и создаются для того чтобы инкапсулировать семантически родственные связи.
И что? Это как то снимает проблему меньшей контроллируемости связей внутри класса и нелинейного роста сложности при увеличении числа связей?
S> Чтобы они не были очень страшными, сле дует придерживаться определенный принципов проектирования
Следует. Но чем больше в этом участвует компилятор, тем лучше.
S>>> Нет никакой нужды защищать метод foo() от изменений в классе, когда этот метод принадлежит самому классу.
AVK>>Обосновать можешь? S>Могу. Принцип черного ящика
Принцип черного ящика не применим, если мы говорим об внутреннем устройстве собственно ящика.
S>, метод инкапсулирован в класс
И что?
S>, не имеет значения, что и как происходит внутри, важен только интерфейс.
Это все понятно. Непонятно, почему, если метод пользуется только публичным контрактом обрабатываемого класса, то начинает иметь значение все, что происходит внутри этого метода? Хелпер точно так же выставляет наружу контракт, а детали реализации прячет внутри.
S>>>Нет, критерии тут тоже четкие. Функция работает с состоянием класса значит это функция класса
AVK>>Обосновать можешь? S>Собственно целый день этим и занимаюсь. Имеющий глаза увидит.
Поскольку глаза у меня есть, а увидеть не увидел, то твое утверждение неверно.
AVK>>Причину тебе уже неоднократно называли. Могу повторить — как и у подавляющего большинства архитектурных правил, это уменьшение связности кода. S>Вместо одного класса два. Уменьшили, блин.
Да, конечно. Связи между классами намного слабее и жестче контроллируются, нежели связи внутри одного класса. Тут есть о чем спорить?
... << RSDN@Home 1.2.0 alpha 4 rev. 1095 on Windows Vista 6.0.6001.65536>>
public class Order
{
public OrderLine Lines {get; set;}
public decimal getTotal()
{
decimal res = 0;
foreach(OrderLine line in lines)
res += line.getSubTotal();
return res;
}
}
public class OrderLine
{
public Product Product {get; set;}
public decimal Count {get; set;}
public decimal Price {get;set}
public decimal getSubTotal()
{
return Count * (Price + Product.TaxRate/100 * Price);
}
}
Методы getTotal и getSubTotal используют только публичный интерфейс классов. Значит я долженн вынести их в вот такой класс:
public class OrderCalcHelper
{
public decimal getSubTotal(OrderLine line)
{
return line.Count * (line.Price + line.Product.TaxRate/100 * line.Price);
}
publi decimal getTotal(Order order)
{
decimal res = 0;
foreach(OrderLine line in order.lines)
res += line.getSubTotal();
return res;
}
}
Теперь, если кто умеет, посчитайте метрики связности кода для случая с классом OrderCalcHelper и без него.
Рассмотрим кейс, когда мы используем класс Order и нам нужно получить сумму по заказу. Я вижу Order и мне нужна сумма, и ничто мне не говорит здесь, что где-то есть замечательный хелпер, который умеет считать нужную мне сумму (вероятно однажды замучившись искать подобные хелперы, кто-то в Microsoft придумал методы-расширения).
Рассмотрим кейс внесения изменения в контракт классов Order, OrderLine. Компилятор нам поможет в случае с OrderCalcHelper, но неужели компилятор становится бессильным в его отсутствии?
Напоследок небольшое наблюдение из практики. В проектах, использующих ADO.NET часто можно встретить различные SQL-хелперы. Они помогают конвертировать значения, работать с ридерами, с SQL параметрами и т.д. и т.п. Надо заметить, что наличие таких хелперов безусловно полезно. Но интересно другое. Если система достаточно велика, и (или) стара, так чтобы над ней работали более 4 программистов, практически гарантировано при code review можно найти парочку хелперов, которые делают одно и то-же. Это и есть основная проблема хэлперов, которую грубо называют размазывание логики.
Здравствуйте, stump, Вы писали:
S>Теперь, если кто умеет, посчитайте метрики связности кода для случая с классом OrderCalcHelper и без него.
Очень мне было интересно, прикинуть метрики. Вот результат расчета VS2008:
С Helper'ом Namespace: Test
Maintainability Index: 93
Cyclomatic Complexity: 19
Depth of Inheritance: 1
Class Coupling: 7
Lines of Code: 22
Без Helper'а Namespace: Test
Maintainability Index: 92
Cyclomatic Complexity: 18
Depth of Inheritance: 1
Class Coupling: 6
Lines of Code: 21
Получается по расчетам VS, сопровождаемость программы с Helper'ом лучше чем без него
AVK>>Как это ноль? Про связи внутри класса ты уже забыл? Зря, они как раз самые страшные. S>В software design под связностью (coupling) понимают связи между структурными единицами (классами, модулями). S>Классы как раз и создаются для того чтобы инкапсулировать семантически родственные связи. Чтобы они не были очень страшными, сле дует придерживаться определенный принципов проектирования (SRP к примеру).
Следуя твоей логике наименьшая связанность будет у программы с main и одним классом/функцией. Очевидный для меня абсурд.
Здравствуйте, stump, Вы писали:
S>Один класс — ноль связей. Два класса — одна связь. Один > ноль.
Дай я продолжу твою мысль.. Если мы вообще весь код поместим в один класс, то у нас получится идеальная программа с точки зрения связности.. Гениально! =)
Проблема связности не в публичных связях между классами, а в неявных связях как внутри одной сущности, так и между ними, на что тебе собственно Андрей уже намекал.
К слову, классический DI, если следовать твоей логике, увеличивает связность минимум в трое..
S>Тв путаешь инкапсуляцию и изоляцию.
Изоляция — это вообще термин из области CAS и контроля памяти, так что если тут кто что и путает, то точно не я..
Я описал почти классический пример, для чего в принципе нужна инкапсуляция.
S> Нет никакой нужды защищать метод foo() от изменений в классе, когда этот метод принадлежит самому классу.
То есть, тот факт, что метод физически впихнули внутрь класса, автоматически делает его реализацию корректной?
Я уже приводил пример к чему может привести подобный подход...
S>Очень убедительно.... "Они ошибались", почему?
Ну, я же не буду тебе всего Меерса цитировать.. ) Почитай, там много полезного, он довольно подробно все это разжевывает..
S>Нет, критерии тут тоже четкие. Функция работает с состоянием класса значит это функция класса,
Отличный критерий! С состоянием класса, так или иначе, работает все, что работает с этим классом.
Сериализатор работает с состоянием? Конечно, пихаем в класс. Сохранятор в БД работает? Безусловно, кладем. UI работает? Ну, да, а что же он показывает, как не состояние? Конечно засовываем в класс! =))
Мы опять приходим к одному большему классу на всю программу, в последовательности тебе не откажешь..
S> плюс принцип single responsibility.
Причем он тут?
S>Критерии того когда функцию стоит выносить в хелпер я тут уже описывал.
В хелпер класс можно выносить аспекты, не свойственные для самого класса
Ты вот это называешь четким критерием? Прости, но четкости, равно как и причинности, я тут что-то не улавливаю. Какие аспекты свойственны для класса, а какие нет?
S>Вамое главное что мои критерии причинные,
Так в чем причина? Вот лично я, убей байт, не вижу причину по которой "код форматирования данных класса для сериализации" является не свойственным аспектом для класса, а "рассчет размера" — свойственным.
S> а твои хоть и четкие но причины ты для них сформклировать не можешь.
Могу еще раз напомнить...
В Software Design есть такой термин, как связность (coupling), который определяет на самом деле вовсе не количество связей между структурными единицами (классами), так связность только студия трактует, когда метрики считает, а количество кода, которое ты можешь сломать внеся изменение в одну строчку. Определение, конечно не формальное, зато доходчивое. Формальное же определение связности пляшет не просто от количества связей между модулями, а от количества неконтролируемых связей: Low coupling refers to a relationship in which one module interacts with another module through a stable interface and does not need to be concerned with the other module's internal implementation.
Как видишь, с контролируемыми связями все в порядке, проблема с неконтролируемыми.
Чтобы связность как-то контролировать, придумали такую штуку, как инкапсуляция. Можно выделить публичный контракт и любое внутреннее изменение, гарантировано не влияет на то, что находится за пределами контракта. Таким образом неконтролируемая связь между строчками кода, частично трансформируется в контролируемую компилятором связь между сущностями, оборудованными публичным контрактом. Следовательно, внеся foo() в A — ты теряешь контроль над связью, что приводит к увеличиению связности, вынеся же foo() за пределы A — ты обретаешь контроль и уменьшаешь связность.
Иными словами, у тебя есть два модуля — A и foo(). Когда A и foo() торчат в пределах одного класса, то ты не как не можешь быть уверенным — foo() concerned with the A internal implementation или еще нет. А вот если они разнесены по разным классам, то они гарантировано interacts through a stable interface, что способствует низкой связности.
Я достаточно подробно сформулировал причину?
Здравствуйте, stump, Вы писали:
S>Здравствуйте, IB, Вы писали:
S>Вот вам пример. Заказ: S>
S>public class Order
S>{
S> public OrderLine Lines {get; set;}
S> public decimal getTotal()
S> {
S> decimal res = 0;
S> foreach(OrderLine line in lines)
S> res += line.getSubTotal();
S> return res;
S> }
S>}
S>public class OrderLine
S>{
S> public Product Product {get; set;}
S> public decimal Count {get; set;}
S> public decimal Price {get;set}
S> public decimal getSubTotal()
S> {
S> return Count * (Price + Product.TaxRate/100 * Price);
S> }
S>}
S>
S>Методы getTotal и getSubTotal используют только публичный интерфейс классов. Значит я долженн вынести их в вот такой класс: S>
S>public class OrderCalcHelper
S>{
S> public decimal getSubTotal(OrderLine line)
S> {
S> return line.Count * (line.Price + line.Product.TaxRate/100 * line.Price);
S> }
S> public decimal getTotal(Order order)
S> {
S> decimal res = 0;
S> foreach(OrderLine line in order.lines)
S> res += line.getSubTotal();
S> return res;
S> }
S>}
S>
А теперь представь, появилось новое бизнес требование . TaxRate теперь не свойство продукта, а вычисляется в зависимости от клиента (страна/штат и т.д, физическое лицо/юридическое лицо, и т.д.). Теперь какой подход лучше?
Здравствуйте, stump, Вы писали:
S>Теперь, если кто умеет, посчитайте метрики связности кода для случая с классом OrderCalcHelper и без него.
Всего у нас четыре модуля — два класса и два метода. Следуя формальному определению связности (Low coupling refers to a relationship in which one module interacts with another module through a stable interface and does not need to be concerned with the other module's internal implementation), получаем:
В первом случае у нас две неявных связи и ни одной посредством публичного контракта, то есть, методы concerned with the other module's internal implementation — ни о какой низкой связности речь не идет. Во втором, две связи посредством публичного контракта, то есть one module interacts with another module through a stable interface, и ни одной неявной.
Счет, очевидно, два-ноль в пользу выноса обсуждаемых функций в хелперный класс, ЧТД. =))
S>Я вижу Order и мне нужна сумма, и ничто мне не говорит здесь, что где-то есть замечательный хелпер, который умеет считать нужную мне сумму (вероятно однажды замучившись искать подобные хелперы, кто-то в Microsoft придумал методы-расширения).
Во-первых тебе говорят методы расширения, а во-вторых, тебе говорит знание имеющихся в наличии сервисов. Подобная архитектура очень быстро приучает изучить доступный инструментарий, а не изобретать что-то свое.
S>Рассмотрим кейс внесения изменения в контракт классов Order, OrderLine. Компилятор нам поможет в случае с OrderCalcHelper, но неужели компилятор становится бессильным в его отсутствии?
Угу, становится, особенно, когда у класса есть приватное состояние, жизненный цикл больше недели и правят его код разные разработчики.
Могу еще один кейс привести.
Ты знаком с той концепцией, что при добавлении нового функционала надо не модифицировать имеющийся код, а дописывать новый? Допустим, теперь появился альтернативный способ рассчета Total и Subtotal, ну или какой-нибудь еще агрегат по заказам, позарез нужный аналитикам (а зная аналитиков, таких будет даже не один и не два). Ты будешь вынужден модифицировать существующие классы, я же просто допишу новый внешний сервис.
Это все к вопросу о связности и твоем любимом SRP — держать данные заказа, это одна ответственность, а заниматься аналитикой по заказам — это уже ответственность совершенно другая, таким образом, мы должны завести хелперный класс, хотя бы следуя SRP.. =)
S>Напоследок небольшое наблюдение из практики.
<...> S> Это и есть основная проблема хэлперов, которую грубо называют размазывание логики.
Могу поделиться своим. На моей практике, на таких проектах хелперов не остается вообще. Жизненный цикл типичного метода выглядит примерно так: Сначала он выносится в хелпер, потом хелпер обрастает еще парой-тройкой методов, потом этот хелпер трансформируется в обычный сервис реализующий функционал типичный для данного семейства объектов, и подключаемый посредством DI или какого-либо еще IoC. И большая часть кода состоит из таких вот сервисов — очень удобно.. )
Здравствуйте, IB, Вы писали:
IB>Это все к вопросу о связности и твоем любимом SRP — держать данные заказа, это одна ответственность, а заниматься аналитикой по заказам — это уже ответственность совершенно другая, таким образом, мы должны завести хелперный класс, хотя бы следуя SRP.. =)
А почему вы считаете что ответственность класса Order это только держать данные для заказа? Так можно начать про что угодно говорить. Давайте скажем что ответственность класса String — держать набор символов, а все методы нужно вынести в хелперный класс (сервис) и будет у нас StringService.GetStringLength(), StringService.IndexOf(), StringService.StringToUpper() и т.д.
MC>А почему вы считаете что ответственность класса Order это только держать данные для заказа? Так можно начать про что угодно говорить. Давайте скажем что ответственность класса String — держать набор символов, а все методы нужно вынести в хелперный класс (сервис) и будет у нас StringService.GetStringLength(), StringService.IndexOf(), StringService.StringToUpper() и т.д.
Ну дык на протяжении 5 лет , кто тольок это не предлагал
Здравствуйте, minorlogic, Вы писали:
M>Здравствуйте, stump, Вы писали:
AVK>>>Как это ноль? Про связи внутри класса ты уже забыл? Зря, они как раз самые страшные. S>>В software design под связностью (coupling) понимают связи между структурными единицами (классами, модулями). S>>Классы как раз и создаются для того чтобы инкапсулировать семантически родственные связи. Чтобы они не были очень страшными, сле дует придерживаться определенный принципов проектирования (SRP к примеру).
M>Следуя твоей логике наименьшая связанность будет у программы с main и одним классом/функцией. Очевидный для меня абсурд.
Этот абсурд ты только что сам придумал. У меня про это ни слова не сказано, вероятно ты ни слова не понял из того, что процитировал выше. Там как раз говорится о том что функциональность делится по разными классам, и чем при этом руководствуются.
Здравствуйте, IB, Вы писали:
IB>Здравствуйте, stump, Вы писали:
S>>Один класс — ноль связей. Два класса — одна связь. Один > ноль. IB>Дай я продолжу твою мысль.. Если мы вообще весь код поместим в один класс, то у нас получится идеальная программа с точки зрения связности.. Гениально! =)
Давай ты не будешь передергивать и за меня делать выводы. IB>Проблема связности не в публичных связях между классами, а в неявных связях как внутри одной сущности, так и между ними, на что тебе собственно Андрей уже намекал.
Я не понимаю что такое неявные связи внутри сущности. Если в классе "строка" свойство "длинна" зависит от свойства "символы" то это неявная связь? Что в ней плохого? От нее надо избавляться?
То что ты, и Андрей называете внутренними неявными связями называется cohession (на русский обычно переводится как "зацепление"). Действительно связность и зацепление находятся в некотором противоречии друг с другом. Большое зацеление так же плохо как и большая связность, но с ним борются другими методами.
IB>К слову, классический DI, если следовать твоей логике, увеличивает связность минимум в трое..
Да DI увеличивает связность и это оправдано, поскольку он решает ряд других проблем.
S>>Тв путаешь инкапсуляцию и изоляцию. IB>Изоляция — это вообще термин из области CAS и контроля памяти, так что если тут кто что и путает, то точно не я.. IB>Я описал почти классический пример, для чего в принципе нужна инкапсуляция.
S>> Нет никакой нужды защищать метод foo() от изменений в классе, когда этот метод принадлежит самому классу. IB>То есть, тот факт, что метод физически впихнули внутрь класса, автоматически делает его реализацию корректной? IB>Я уже приводил пример к чему может привести подобный подход...
Компилятору без разницы где лежит метод, внутри класса или вне класса. S>>Очень убедительно.... "Они ошибались", почему?
IB>Ну, я же не буду тебе всего Меерса цитировать.. ) Почитай, там много полезного, он довольно подробно все это разжевывает..
Надо приводить аргументы, а не "авторитетное мнение". А то как в анекдоте получается "- Василий Иваныч, а ты за какой интернационал? — А Ленин за какой?, вот и я за этот!"
S>>Нет, критерии тут тоже четкие. Функция работает с состоянием класса значит это функция класса, IB>Отличный критерий! С состоянием класса, так или иначе, работает все, что работает с этим классом. IB>Сериализатор работает с состоянием? Конечно, пихаем в класс. Сохранятор в БД работает? Безусловно, кладем. UI работает? Ну, да, а что же он показывает, как не состояние? Конечно засовываем в класс! =)) IB>Мы опять приходим к одному большему классу на всю программу, в последовательности тебе не откажешь..
S>> плюс принцип single responsibility. IB>Причем он тут?
Да при том, что сериализация, сохранение в БД, UI не относятся к responsibility того класса, который мы обсуждаем. В последовательности мне не откажешь
S>>Критерии того когда функцию стоит выносить в хелпер я тут уже описывал. IB>
IB>В хелпер класс можно выносить аспекты, не свойственные для самого класса
IB>Ты вот это называешь четким критерием? Прости, но четкости, равно как и причинности, я тут что-то не улавливаю. Какие аспекты свойственны для класса, а какие нет?
Плохо, что не улавливаешь. Почитай про GRASP паттерны проектирования. Но вообще-то для этого нужна практика, и критический подход ко всему что делаешь.
S>>Вамое главное что мои критерии причинные, IB>Так в чем причина? Вот лично я, убей байт, не вижу причину по которой "код форматирования данных класса для сериализации" является не свойственным аспектом для класса, а "рассчет размера" — свойственным.
Тут все очевидно, класс создавался для представления каких-то там данных, класс агрегирует в себе какое-то количество данных, класс может предоставить любую часть этих данных для чтения (записи) другим классам, класс сообщает каков размер содержащихся в нем данных. Все это "свойственно" для данного класса. Это SRP. Форматирование для сериализации — это не свойственно для класса, поскольку это другой тип ответственности (один у него уже есть, другой надо выносить во внешний класс).
S>> а твои хоть и четкие но причины ты для них сформклировать не можешь. IB>Могу еще раз напомнить... IB>В Software Design есть такой термин, как связность (coupling), который определяет на самом деле вовсе не количество связей между структурными единицами (классами), так связность только студия трактует, когда метрики считает, а количество кода, которое ты можешь сломать внеся изменение в одну строчку. Определение, конечно не формальное, зато доходчивое. Формальное же определение связности пляшет не просто от количества связей между модулями, а от количества неконтролируемых связей: Low coupling refers to a relationship in which one module interacts with another module through a stable interface and does not need to be concerned with the other module's internal implementation. IB>Как видишь, с контролируемыми связями все в порядке, проблема с неконтролируемыми. IB>Чтобы связность как-то контролировать, придумали такую штуку, как инкапсуляция. Можно выделить публичный контракт и любое внутреннее изменение, гарантировано не влияет на то, что находится за пределами контракта. Таким образом неконтролируемая связь между строчками кода, частично трансформируется в контролируемую компилятором связь между сущностями, оборудованными публичным контрактом. Следовательно, внеся foo() в A — ты теряешь контроль над связью, что приводит к увеличиению связности, вынеся же foo() за пределы A — ты обретаешь контроль и уменьшаешь связность. IB>Иными словами, у тебя есть два модуля — A и foo(). Когда A и foo() торчат в пределах одного класса, то ты не как не можешь быть уверенным — foo() concerned with the A internal implementation или еще нет. А вот если они разнесены по разным классам, то они гарантировано interacts through a stable interface, что способствует низкой связности. IB>Я достаточно подробно сформулировал причину?
Спасибо,я давно понял твою аргументацию, но не могу с ней согласиться
Дело в том что если foo() является функцией (в математическом смысле) исключительно только данных класса, то не зависимо от того, где ты ее разместишь, внутри самого класса, или вне, степень зависимости foo() от данных класса не изменяется. Это очевидно. Зато когда foo() находится внутри класса она входит в интерфейс самого класса и как ты говоришь, "любое внутреннее изменение, гарантировано не влияет на то, что находится за пределами контракта". То есть у нас все инкапсулировано. Мы можем менять и внутренне устройство данных класса и реализацию foo() и никакие другие классы это не затронет. Если foo() лежит в другом классе, то появляется связь. Самое печальное в том, что теперь не только интерфейс класса А влияет на функцию B.foo(), но и функция B.foo() влияет на класс А. Если нам понадобиться изменить алгоритм foo() то с большОй долей вероятности это выльется в изменение интерфейса класса A. Если вспомнить о том, что foo() использует только данные класса A, то подобные изменения его интерфейса, это довольно неприятный побочный эффект, которого можно избежать, инкапсулировав foo() в A.
Надеюсь, я достаточно подробно сформулировал свою точку зрения.
Здравствуйте, Ага, Вы писали:
Ага>А теперь представь, появилось новое бизнес требование . TaxRate теперь не свойство продукта, а вычисляется в зависимости от клиента (страна/штат и т.д, физическое лицо/юридическое лицо, и т.д.). Теперь какой подход лучше?
Нет, давай останемся в рамках первоначальных условий. Все мы понимаем, что изменение требований влечет изменение дизайна.
Здравствуйте, MozgC, Вы писали:
IB>>Это все к вопросу о связности и твоем любимом SRP — держать данные заказа, это одна ответственность, а заниматься аналитикой по заказам — это уже ответственность совершенно другая, таким образом, мы должны завести хелперный класс, хотя бы следуя SRP.. =) MC>А почему вы считаете что ответственность класса Order это только держать данные для заказа? Так можно начать про что угодно говорить. Давайте скажем что ответственность класса String — держать набор символов, а все методы нужно вынести в хелперный класс (сервис) и будет у нас StringService.GetStringLength(), StringService.IndexOf(), StringService.StringToUpper() и т.д.
Если бы функциональность класса String менялась каждую неделю, то так бы и сделали.
Ага>А теперь представь, появилось новое бизнес требование . TaxRate теперь не свойство продукта, а вычисляется в зависимости от клиента (страна/штат и т.д, физическое лицо/юридическое лицо, и т.д.). Теперь какой подход лучше?
Не аргумент. Они потребуют практически одинаковой работы по переделке дизайна. TaxRate станет внешним параметром.
Я с интересом слежу за ходом дискуссии, т.к. применяю оба подхода и вижу преимущества и в том и другом случае. Пока выбор той или иной стратегии идет с учетом интуиции. Более формальных правил я придумать не смог.
Аргумент про использование только публичного контракта плохо ложится на использование доменной модели, там все данные являются контрактом. Если взять в качестве закона:
Если для реализации данного функционала достаточно только публичного контракта класса, значит этот функционал должен быть снаружи класса.
Значит единственным правильным решением является anemic domain model. POCO/POJO могут быть хороши в одних ситуациях, но при использовании такой модели меня не покидает мысль о том, что работа с ними ничем не отличается от использования DataSet'ов.