Здравствуйте, Vladek, Вы писали:
V>Это не репозиторий. То, что ты понимаешь под репозиторием, называется шлюзом — https://martinfowler.com/eaaCatalog/gateway.html А ещё точнее, шлюзом к табличным данным: https://martinfowler.com/eaaCatalog/tableDataGateway.html Проблемы, которую он решает (возня с SQL в коде) — у тебя нет, у тебя есть EF и запросы LINQ.
V>Твой репозиторий возвращает живые сущности EF? Если да, то значит ты не стесняешься светить EF по всей системе, а следовательно лишний код в виде репозиториев или шлюзов не нужен — проще инжектить контекст EF куда надо и использовать напрямую без всяких обёрток. Нет репозиториев и шлюзов, не нужно ничего мокать и писать тесты.
нет, это не табличный шлюз т.к. внутри репозитория работает полноценный маппер на доменную модель (EF или самописный), и нет, репозиторий возвращает не EF, а готовые доменные модели, а также может иметь некоторую дополнительную логику по управлению транзакциями, порядком сохранения или другой подобной логикой
Здравствуйте, Stalker., Вы писали:
S>нет, это не табличный шлюз т.к. внутри репозитория работает полноценный маппер на доменную модель (EF или самописный), и нет, репозиторий возвращает не EF, а готовые доменные модели, а также может иметь некоторую дополнительную логику по управлению транзакциями, порядком сохранения или другой подобной логикой
Тогда начнём сначала: с класса User (или Person). Это просто структура данных. Данные есть, поведения нет. Конечно с тестами трудности возникают — тестируют поведение, а его нет. Рефакторим — добавляем в класс метод SendPromotion(). Уже лучше, можно написать пару тестов — на отправку и сбой отправки.
Код сервиса REST упрощается до безобразия:
public bool SendUserPromotion(int userID)
{
if (userID < 0)
throw new InvalidUserID();
User user = UserRepository.GetUser(userID);
return user.SendPromotion();
}
Здесь соблюдены два важных принципа ООП:
1) В ООП лампочка сама себя вкручивает. Посылаешь объекту сообщение и он выполняет работу: user.SendPromotion().
2) В ООП ты не беспокоишься как лампочка вкрутит себя. Объект user выполнит проверку своего состояния (возраста) сам.
Куда девались остальные вызовы? Они внутри SendPromotion. Как внешние зависимости попали в объект user? Об этом позаботился UserRepository. Как это тестировать? Замокать внешние зависимости объекта user, установить нужное состояние user, вызвать метод SendPromotion, проверить изменения состояния user и моков.
Класс UserRepository не имеет лишних методов — весь код UpdateUserLastPromotionDate(userID) может быть в самом классе User или его наследнике. Главное, код сервиса REST от этих деталей не зависит. Весь конкретный код работы с User сосредоточен в самом классе User и его наследниках — прыгать по толпе файлов, чтобы выяснять детали, не приходится.
Здравствуйте, Vladek, Вы писали:
V>Тогда начнём сначала: с класса User (или Person). Это просто структура данных. Данные есть, поведения нет. Конечно с тестами трудности возникают — тестируют поведение, а его нет.
Если нет поведения — нет необходимости в тестировании. Использование структуры автоматически покрывается при написании тестов тестирующее поведение.
V>Рефакторим — добавляем в класс метод SendPromotion().
Особенно это будет криво выглядеть когда для реализации SendPromotion понадобятся еще доаолнительные домен сушности и возникнет вопрос, а чья же это собстевенно отвественность?
V>Уже лучше, можно написать пару тестов — на отправку и сбой отправки.
С таким же успехом можно написать тесты на сервис которые реализует SendPromotion.
V>Здесь соблюдены два важных принципа ООП: V>1) В ООП лампочка сама себя вкручивает. Посылаешь объекту сообщение и он выполняет работу: user.SendPromotion().
Инкапсуляция и полиморфизм не подразумевает, что ламочка сама себя вкручивает. Ты, кстати, где-то видел обратное?
V>2) В ООП ты не беспокоишься как лампочка вкрутит себя.
Никакой связи с ООП. Такой подход вообще не работает когда есть отношение многие-ко-многим.
V>Объект user выполнит проверку своего состояния (возраста) сам.
В общем случае обьект не может выполнить такую проверку, поскольку отсуствует контекст.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, AndrewJD, Вы писали:
AJD>Здравствуйте, Vladek, Вы писали:
V>>Тогда начнём сначала: с класса User (или Person). Это просто структура данных. Данные есть, поведения нет. Конечно с тестами трудности возникают — тестируют поведение, а его нет. AJD>Если нет поведения — нет необходимости в тестировании. Использование структуры автоматически покрывается при написании тестов тестирующее поведение.
Полное покрытие кода тестами не нужно. Классы с полями и методами удобнее и понятнее анемичных структур данных.
V>>Рефакторим — добавляем в класс метод SendPromotion(). AJD>Особенно это будет криво выглядеть когда для реализации SendPromotion понадобятся еще доаолнительные домен сушности и возникнет вопрос, а чья же это собстевенно отвественность?
Объекты могут включать в себя другие объекты или ссылаться на них. В DDD их называют агрегатами, например.
V>>Уже лучше, можно написать пару тестов — на отправку и сбой отправки. AJD>С таким же успехом можно написать тесты на сервис которые реализует SendPromotion.
Сервис. Список файлов *Service.cs — это просто процедурные химеры. Приносятся в жертву богу рефакторинга.
V>>Здесь соблюдены два важных принципа ООП: V>>1) В ООП лампочка сама себя вкручивает. Посылаешь объекту сообщение и он выполняет работу: user.SendPromotion(). AJD>Инкапсуляция и полиморфизм не подразумевает, что ламочка сама себя вкручивает. Ты, кстати, где-то видел обратное?
Не распарсил. https://pragprog.com/articles/tell-dont-ask
V>>2) В ООП ты не беспокоишься как лампочка вкрутит себя. AJD>Никакой связи с ООП. Такой подход вообще не работает когда есть отношение многие-ко-многим.
Не распарсил. https://pragprog.com/articles/tell-dont-ask
V>>Объект user выполнит проверку своего состояния (возраста) сам. AJD>В общем случае обьект не может выполнить такую проверку, поскольку отсуствует контекст.
У объекта есть идентичность, состояние, поведение — в общем случае. Как ни странно, но этого достаточно для проверок! Про контекст не распарсил.
Здравствуйте, Vladek, Вы писали:
V>Полное покрытие кода тестами не нужно. Классы с полями и методами удобнее и понятнее анемичных структур данных.
Полнота покрытия тут не причем. Никто не запрещает иметь классы с полями и методами — речь идет о разделении бизнес логики и доменных обьектов.
V>Объекты могут включать в себя другие объекты или ссылаться на них. В DDD их называют агрегатами, например.
Получаем на выходе либо GOD классы или искусственные сущности типа агрегатов.
AJD>>С таким же успехом можно написать тесты на сервис которые реализует SendPromotion. V>Сервис. Список файлов *Service.cs — это просто процедурные химеры. Приносятся в жертву богу рефакторинга.
Когда речь идет про религию — аргументы безполезны.
V>Не распарсил. https://pragprog.com/articles/tell-dont-ask
Где ты видел чтобы лампочка себя вкручивала
V>У объекта есть идентичность, состояние, поведение — в общем случае. Как ни странно, но этого достаточно для проверок!
Как ни странно, этого не достаточно для сценариев отличных от примитивных.
V>Про контекст не распарсил.
Обьект не знает в каком контексте(сценарии) его использует и его окружение. Это знает код который его использует. Конечно, можно использовать GOD класс который знает все — но мы вроде про хороший дизайн тут говорим.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, Vladek, Вы писали:
V>Тогда начнём сначала: с класса User (или Person). Это просто структура данных. Данные есть, поведения нет.
Логика там есть — CalculateAge(), также там будет как минимум логика валидации (имя не пустое, дата рождения не в будущем итп), и другая логика связанная только с самим юзером и другими доменными классами связанными с ним. Юнит-тестирование и будет делаться для указанных методов
V>Конечно с тестами трудности возникают — тестируют поведение, а его нет. Рефакторим — добавляем в класс метод SendPromotion(). Уже лучше, можно написать пару тестов — на отправку и сбой отправки.
Откуда в доменной модели взялись вызовы почтового сервиса и логирования? Это уже не доменная модель, а гибрид с сервисом какой-то, так можно и вызовы репозитория туда-же впихнуть и класс сервиса станет вообще ненужным. И репозиторий не может ничего инджектить никуда, у него другая роль — работа с БД
V>Куда девались остальные вызовы? Они внутри SendPromotion. Как внешние зависимости попали в объект user? Об этом позаботился UserRepository. Как это тестировать? Замокать внешние зависимости объекта user, установить нужное состояние user, вызвать метод SendPromotion, проверить изменения состояния user и моков.
V>Класс UserRepository не имеет лишних методов — весь код UpdateUserLastPromotionDate(userID) может быть в самом классе User или его наследнике. Главное, код сервиса REST от этих деталей не зависит. Весь конкретный код работы с User сосредоточен в самом классе User и его наследниках — прыгать по толпе файлов, чтобы выяснять детали, не приходится.
код апдейта даты рассылки это чисто работа репозитория, у доменной модели при необходимости может быть поле с такой датой, а апдейт в БД пойдет из репозитория, который вызовет workflow сервис. По-другому это вообще не сможет работать т.к. такой апдейт может идти совместно с другими апдейтами в транзакции, и нагрузив это все в доменную модель она полностью превратится в workflow сервис с процедурным кодом внутри и кучей private методов куда и переедет собственно бизнес логика
Здравствуйте, AndrewJD, Вы писали:
AJD>Здравствуйте, Vladek, Вы писали:
V>>Полное покрытие кода тестами не нужно. Классы с полями и методами удобнее и понятнее анемичных структур данных. AJD>Полнота покрытия тут не причем. Никто не запрещает иметь классы с полями и методами — речь идет о разделении бизнес логики и доменных обьектов.
Доменные объекты — средство реализации бизнес-логики. Это одно и то же.
V>>Объекты могут включать в себя другие объекты или ссылаться на них. В DDD их называют агрегатами, например. AJD>Получаем на выходе либо GOD классы или искусственные сущности типа агрегатов.
Мы моделируем предметную область. Моделируем ровно до такой степени, чтобы решить поставленную задачу. Агрегат — это не сущность, это роль модели.
AJD>>>С таким же успехом можно написать тесты на сервис которые реализует SendPromotion. V>>Сервис. Список файлов *Service.cs — это просто процедурные химеры. Приносятся в жертву богу рефакторинга. AJD>Когда речь идет про религию — аргументы безполезны.
В ООП именно такой вывернутый наизнанку подход к моделированию позволяет создавать рабочие, простые и понятные модели. Субъект, манипулирующий объектами, отсутствует.
V>>У объекта есть идентичность, состояние, поведение — в общем случае. Как ни странно, но этого достаточно для проверок! AJD>Как ни странно, этого не достаточно для сценариев отличных от примитивных.
Talk is cheap. Show me the code.
Linus Torvalds
V>>Про контекст не распарсил. AJD>Обьект не знает в каком контексте(сценарии) его использует и его окружение. Это знает код который его использует. Конечно, можно использовать GOD класс который знает все — но мы вроде про хороший дизайн тут говорим.
Да, код должен быть независим от внешнего окружения. Именно это позволяет использовать его в разных окружениях!
Здравствуйте, Stalker., Вы писали:
S>Здравствуйте, Vladek, Вы писали:
V>>Тогда начнём сначала: с класса User (или Person). Это просто структура данных. Данные есть, поведения нет.
S>Логика там есть — CalculateAge(), также там будет как минимум логика валидации (имя не пустое, дата рождения не в будущем итп), и другая логика связанная только с самим юзером и другими доменными классами связанными с ним. Юнит-тестирование и будет делаться для указанных методов
Этот метод не меняет состояние объекта, это простой геттер.
V>>Конечно с тестами трудности возникают — тестируют поведение, а его нет. Рефакторим — добавляем в класс метод SendPromotion(). Уже лучше, можно написать пару тестов — на отправку и сбой отправки.
S>Откуда в доменной модели взялись вызовы почтового сервиса и логирования? Это уже не доменная модель, а гибрид с сервисом какой-то, так можно и вызовы репозитория туда-же впихнуть и класс сервиса станет вообще ненужным. И репозиторий не может ничего инджектить никуда, у него другая роль — работа с БД
Если есть задача рассылать письма пользователям, то логично это явно отразить в коде. А сервис — что это? Чистой воды абстракция, которая не нужна. Репозиторий достаёт из небытия нужные объекты. Бд там или нет — детали реализации.
V>>Куда девались остальные вызовы? Они внутри SendPromotion. Как внешние зависимости попали в объект user? Об этом позаботился UserRepository. Как это тестировать? Замокать внешние зависимости объекта user, установить нужное состояние user, вызвать метод SendPromotion, проверить изменения состояния user и моков. V>>Класс UserRepository не имеет лишних методов — весь код UpdateUserLastPromotionDate(userID) может быть в самом классе User или его наследнике. Главное, код сервиса REST от этих деталей не зависит. Весь конкретный код работы с User сосредоточен в самом классе User и его наследниках — прыгать по толпе файлов, чтобы выяснять детали, не приходится.
S>код апдейта даты рассылки это чисто работа репозитория, у доменной модели при необходимости может быть поле с такой датой, а апдейт в БД пойдет из репозитория, который вызовет workflow сервис. По-другому это вообще не сможет работать т.к. такой апдейт может идти совместно с другими апдейтами в транзакции, и нагрузив это все в доменную модель она полностью превратится в workflow сервис с процедурным кодом внутри и кучей private методов куда и переедет собственно бизнес логика
да, доменная модель это и есть бизнес-логика. Транзакции, workflow (что это?) — всё это модель предметной области может спокойно использовать сама. Не нужно распылять код, решающий бизнес-задачу, на совершенно абстрактные понятия типа workflow или транзакций. Это всё технические детали. Если технические детали диктуют тебе как моделировать предметную область, то никакой внятной модели не получится и понять, что же происходит можно будет лишь во время отладки, прыгая по стеку вызовов.
Нормальная архитектура похожа на луковицу — в центре бизнес-логика, то есть модель предметной области. Это код, который делает ровно то, что просил заказчик. Все понятия, которыми оперирует заказчик, отражены в этом коде. Технические детали уходят на второй план.
Объект user отправил успешно письмо, — он же сохраняет этот факт в бд или ещё куда. Это информация, которой он владеет, его же и ответственность. Как это делается — напрямую, через наследование, через внешние зависимости — это уже детали. Дажде если они поменяеются, код, который использует user от это не поменяется.
Луковица — меняем внешние слои, внутренние не меняются; меняем внутренние слои, внешние не меняются.
Здравствуйте, Vladek, Вы писали:
V> да, доменная модель это и есть бизнес-логика. Транзакции, workflow (что это?) — всё это модель предметной области может спокойно использовать сама. Не нужно распылять код, решающий бизнес-задачу, на совершенно абстрактные понятия типа workflow или транзакций. Это всё технические детали. Если технические детали диктуют тебе как моделировать предметную область, то никакой внятной модели не получится и понять, что же происходит можно будет лишь во время отладки, прыгая по стеку вызовов.
у тебя слишком нетипичный взгляд на то, что из себя бизнес-логика представляет, дискутировать особо смысла не имеет, я придерживаюсь более стандартного подхода к разделению логики, который пропогандируется тем-же Фаулером и Майкрософт, где бизнес-логика не отвечает за вызов почтового сервера, а только генерирует условия и сообщает сервису надо-ли это делать или нет, и уж тем более не лезет в такие вещи как транзакции и порядок сохранения
еще, может кто-то прояснит какой собственно фреймворк сейчас модно юзать для RESTful сервисов, я так понимаю старый добрый WCF ушел в прошлое, вместо него пришел Web API, но сейчас смотрю уже пишут про ASP.NET Core. Кто как к этому делу подходит?
Здравствуйте, Stalker., Вы писали:
S>еще, может кто-то прояснит какой собственно фреймворк сейчас модно юзать для RESTful сервисов, я так понимаю старый добрый WCF ушел в прошлое, вместо него пришел Web API, но сейчас смотрю уже пишут про ASP.NET Core. Кто как к этому делу подходит?
Здравствуйте, Sharov, Вы писали:
S>Я NancyFx использую. web api вроде есть в wcf?
нас в основном майкрософтовские фреймворки интересуют, я так понимаю выбор для Rest сервисов в основном между WCF, Web API и каким-то новым ASP.NET Core.
WCF не содержит Web API, это разные технологии, но обе позволяют рест сервисы писать
Здравствуйте, Stalker., Вы писали:
S>нас в основном майкрософтовские фреймворки интересуют, я так понимаю выбор для Rest сервисов в основном между WCF, Web API и каким-то новым ASP.NET Core.
S>WCF не содержит Web API, это разные технологии, но обе позволяют рест сервисы писать
Есть WCF DataServices которые поддерживают OData и позволяет делать REST.
Но все WCF-based категорически не рекомендую для новых проектов потому что это ужасный легаси, который не развивается уже лет 5.
ASP.NET WebAPI — стабильный продукт, основанный на ASP.NET
ASP.NET Core WebAPI — пока еще сыроватый кроссплатформенный продукт, развитие ASP.NET WebAPI и (почти) полностью с ним же совместимый. Если аффтар любит модное-молодежное, и вероятность попасть на косяки фрйемворка его нее смущает — то не вижу причин не использовать
Здравствуйте, Vladek, Вы писали:
V>Доменные объекты — средство реализации бизнес-логики. Это одно и то же.
Если используеся жирная модель — да, если не используется то нет.
V>Мы моделируем предметную область. Моделируем ровно до такой степени, чтобы решить поставленную задачу. Агрегат — это не сущность, это роль модели.
Это все философия. На практике это вспомагательная хрень нужная для обхода кривости жирной модели данных.
AJD>>Когда речь идет про религию — аргументы безполезны. V>Объекты *Service — это карго-культ.
Где аргументы?
V>В ООП именно такой вывернутый наизнанку подход к моделированию позволяет создавать рабочие, простые и понятные модели. Субъект, манипулирующий объектами, отсутствует.
Эта можель не работает когда нужно обеспечить взаимодействие нескольких сущностей.
V>Да, код должен быть независим от внешнего окружения. Именно это позволяет использовать его в разных окружениях!
Засовываение логики в сущность гвоздями прибивает конкретный сценарий его использования.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, AndrewJD, Вы писали:
AJD>Где аргументы?
Этот вопрос надо самому себе прежде всего задавать. Что ты добавил в дискуссию кроме набора догматичных и малопонятных заявлений? Читай про разницу между ПП и ООП. Я писал о ООП.
Здравствуйте, Vladek, Вы писали:
V>Объект user отправил успешно письмо, — он же сохраняет этот факт в бд или ещё куда. Это информация, которой он владеет, его же и ответственность. Как это делается — напрямую, через наследование, через внешние зависимости — это уже детали. Дажде если они поменяеются, код, который использует user от это не поменяется.
Вот в этом и проблема такого подхода. Если объект user и письмо отправляет, и в БД пишет, то код который использует user может и не поменяется, зато код отравки письма может запросто поменять логику записи в БД, причем зачастую без ведома разработчика.
Существует несколько видов связности кода. Вы пропагандируете тот, который вокруг сущности, который, как это ни парадоксально, приводит к худшей инкапсуляции, а есть функциональный, который, на самом деле, инкапсуляцию повышает.
Можно рассматривать код, который занимается только работой с БД и только отправкой писем, как сервисы, но на самом деле, это полноценные объекты. У них есть все что надо, включая состояние. А объекты типа user (без логики) как сообщения которыми они обменияваются. И тогда все становится на свои места, с точки зрения идеологии, вот и ООП в полный рост, и связность низкая, и инкапсуляция высокая. А главное поддерживать такой код существенно проще.
Здравствуйте, Vladek, Вы писали:
V>Этот вопрос надо самому себе прежде всего задавать. Что ты добавил в дискуссию кроме набора догматичных и малопонятных заявлений?
Мне лень переливать из пустого в порожнее. Почитай в этой форуме мега типики про анемик-vs-жирная модель.
V>Читай про разницу между ПП и ООП. Я писал о ООП.
Попытайся осознать простой факт, что используя один и тот же обьектно-ориентированный язык, но используя разные модели данных ты получаешь кардинально разную архитектуру.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, IB, Вы писали:
IB>Здравствуйте, Vladek, Вы писали:
V>>Объект user отправил успешно письмо, — он же сохраняет этот факт в бд или ещё куда. Это информация, которой он владеет, его же и ответственность. Как это делается — напрямую, через наследование, через внешние зависимости — это уже детали. Дажде если они поменяеются, код, который использует user от это не поменяется. IB>Вот в этом и проблема такого подхода. Если объект user и письмо отправляет, и в БД пишет, то код который использует user может и не поменяется, зато код отравки письма может запросто поменять логику записи в БД, причем зачастую без ведома разработчика.
Код отправки письма меняет логику записи в БД без ведома разработчика — я что-то такое встречал в романе "Daemon" Дэниэла Суреза, но это была фантастика.
IB>Существует несколько видов связности кода. Вы пропагандируете тот, который вокруг сущности, который, как это ни парадоксально, приводит к худшей инкапсуляции, а есть функциональный, который, на самом деле, инкапсуляцию повышает. IB>Можно рассматривать код, который занимается только работой с БД и только отправкой писем, как сервисы, но на самом деле, это полноценные объекты. У них есть все что надо, включая состояние. А объекты типа user (без логики) как сообщения которыми они обменияваются. И тогда все становится на свои места, с точки зрения идеологии, вот и ООП в полный рост, и связность низкая, и инкапсуляция высокая. А главное поддерживать такой код существенно проще.
Отправка писем и запись в бд — зависимости объекта User, который обменивается с ними простыми структурами данных (готовыми письмами и заполненными сущностями EF). Отправлялка писем ничего не знает про БД, контекст БД ничего не знает про письма, оркестром управляет объект User в методе SendPromotion — название метода сразу нам говорит, зачем вообще весь этот код написан. Вся логика, касающаяся задачи, находится в нём. Эту логику можно тестировать изолированно от отправлялки писем и контекста бд.
Здравствуйте, Vladek, Вы писали:
V>Код отправки письма меняет логику записи в БД без ведома разработчика — я что-то такое встречал в романе "Daemon" Дэниэла Суреза, но это была фантастика.
А я встречаю это в каждом первом DDD проекте.
V>Отправка писем и запись в бд — зависимости объекта User, который обменивается с ними простыми структурами данных (готовыми письмами и заполненными сущностями EF). Отправлялка писем ничего не знает про БД, контекст БД ничего не знает про письма, оркестром управляет объект User в методе SendPromotion — название метода сразу нам говорит, зачем вообще весь этот код написан.
Чтобы нагляднее проиллюстрировать мою мысль, давайте добавим еще один метод — MonthlyCharge(), в котором находится логика помесячного списания денег. Она так же хорошо изолирована от БД и отправки писем.
Проблема только в том, что по вашей логике этот метод тоже должен находиться внутри класса User, а значит он имеет доступ к внутреннему состоянию этого класса. Таким образом, логика SendPromotion() может неявно повлиять на то как списываются деньги за подписку. То есть на лицо нарушение инкапсуляции, за которую вы вроде как ратуете.
На всякий случай, ключевое слово здесь "неявно". Разработчик, совершенно без задней мысли, реализуюя логику списания денег, может поменять состояние класса User для своих нужд, и ему и в голову не придет (да и не должно), что это может отразиться на отправке промо, ну и наоборот.
If you're writing a function that can be implemented as either a member or as a non-friend non-member, you should prefer to implement it as a non-member function. That decision increases class encapsulation. When you think encapsulation, you should think non-member functions.
Здравствуйте, IB, Вы писали:
IB>Здравствуйте, Vladek, Вы писали:
V>>Код отправки письма меняет логику записи в БД без ведома разработчика — я что-то такое встречал в романе "Daemon" Дэниэла Суреза, но это была фантастика. IB>А я встречаю это в каждом первом DDD проекте.
V>>Отправка писем и запись в бд — зависимости объекта User, который обменивается с ними простыми структурами данных (готовыми письмами и заполненными сущностями EF). Отправлялка писем ничего не знает про БД, контекст БД ничего не знает про письма, оркестром управляет объект User в методе SendPromotion — название метода сразу нам говорит, зачем вообще весь этот код написан. IB>Чтобы нагляднее проиллюстрировать мою мысль, давайте добавим еще один метод — MonthlyCharge(), в котором находится логика помесячного списания денег. Она так же хорошо изолирована от БД и отправки писем. IB>Проблема только в том, что по вашей логике этот метод тоже должен находиться внутри класса User, а значит он имеет доступ к внутреннему состоянию этого класса. Таким образом, логика SendPromotion() может неявно повлиять на то как списываются деньги за подписку. То есть на лицо нарушение инкапсуляции, за которую вы вроде как ратуете.
Здесь надо разбираться с границами ответственности объекта User. Пользователи не участвуют в формировании счетов, они их только оплачивают и правила формирования счетов от них зависят только косвенно (как они воспользовались услугами или товарами). Поэтому метод ChargeUser будет использовать объект User, у которого вызовет метод — IssueInvoice, передав уже готовый счёт на оплату. Находиться метод ChargeUser будет в другом объекте — в какой-нибудь истории покупок.
Но это не так важно, важно другое — пользователя класса User не должно беспокоить как его методы реализованы и как они друг на друга влияют, контракт использования класса от этого не зависит. Если IssueInvoice ломает поведение SendPromotion, то это просто ошибка программирования, которую надо исправить. Код, который использует класс User, меняться не будет — потому что все детали скрыты внутри класса User и фикс бага не выйдет за его пределы. Вот это и есть правильная инкапсуляция.
IB>На всякий случай, ключевое слово здесь "неявно". Разработчик, совершенно без задней мысли, реализуюя логику списания денег, может поменять состояние класса User для своих нужд, и ему и в голову не придет (да и не должно), что это может отразиться на отправке промо, ну и наоборот.
Логики списания денег там быть не должно, а весь остальной код манипулирования данными пользователей будет перед глазами в одном файле. Ну а раз весь код сосредоточен в одном месте, то и юнит-тесты писать легко и соответственно ошибки можно заметить быстрее.
IB>На эту тему есть отличная статья Майерса (Effective C++), двадцати летней давности: http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197 IB>
IB>If you're writing a function that can be implemented as either a member or as a non-friend non-member, you should prefer to implement it as a non-member function. That decision increases class encapsulation. When you think encapsulation, you should think non-member functions.
Ну вот выше ChargeUser (MonthlyCharge) мы вынесли за пределы класса User и я попытался объяснить почему ему там не место.