Comments 17
По моему паттерн command, этот антипаттерн, потому что скрывает за командами реальные классы и модули, это усложняет поиск класса которы привязан к команде. И в таком приложении реально добавляет неудобства поддержки.
command, этот антипаттерн, потому что скрывает за командами реальные классы и модули
Ну тогда по вашим словам принципиально любое инкапсулирование логики в классах будет усложнять читаемость. И тогда 90% паттернов -- это антипаттерны, потому что в них предполагается передача зависимостей через конструктор.
это усложняет поиск класса которы привязан к команде
Каким образом? Все зависимости читаются в конструкторе класса (команды в нашем случае). Либо прямо по месту использования можно по одному клику провалиться в реализацию интересующего нас метода.
Ну и паттерн "Команда" не про это вообще -- у команды может и не быть внешних зависимостей, никто не запрещает логику прямо в классе команды реализовать. Он (этот паттерн) нужен исключительно как подход для объединения множества операций под единым интерфейсом.
Например, классический пример -- это реализация undo\redo функционала в приложении. Приложение фиксирует последовательность команд, запущенных пользователем и если все команды закрыты одним интерфейсом
Stack<ICommand> _commandsHistory;
interface ICommand
{
void Undo();
}
то, при необходимости откатить изменения, мы можем просто вытягивать элементы стэка _commandsHistory и вызывать ICommand.Undo(), абстрагировавшись от реализации этих самых команд.
Ну и паттерн "Команда" не про это вообще -- у команды может и не быть внешних зависимостей, никто не запрещает логику прямо в классе команды реализовать. Он (этот паттерн) нужен исключительно как подход для объединения множества операций под единым интерфейсом.
Вообще то нет, вы описываете обычный Virtual Function, а комманда в своём первоначальном описании:
Команда является поведенческим шаблоном, в котором объект используется для инкапсуляции всей информации, необходимой для выполнения действия или вызова события в более позднее время. Эта информация включает в себя имя метода, объект, который является владельцем метода и значения параметров метода.
При чем тут Virtual Function, что из того что я описал хоть как-то может относиться к виртуальным функциям?
И я описал для чего этот паттерн обычно используется, а вы зачем-то в качестве контраргумента привели одно из определений паттерна, это вообще перпендикулярные вещи.
Вы не согласны именно с моим утверждением про то что это паттерн "для объединения множества операций под единым интерфейсом" или с тем что у реализации этого паттерна не обязательно должны быть внешние зависимости?
не согласен с
для объединения множества операций под единым интерфейсом
Единый интерфейс даёт виртуальный метод, поддержка которого встроено во множество языков программирования и не требует отдельных структур.
Единый интерфейс даёт виртуальный метод
Эм, единый интерфейс, как ни странно, определяют при помощи... интерфейсов. А виртуальные методы -- это же просто встроенная возможность языка переопределять методы наследуемого класса, при чем тут они?
Например, у нас в коде определены несколько сущностей с похожим функционалом -- Cat, Dog, Human, реализующие метод Run(). Если мы хотим в коде смоделировать стадион, по которому будут бегать экземпляры наших людей, кошек и собак (т.е. нам не важно как именно бегает конкретный инстанс объекта, на четырех ногах или на двух, нам важно только знать, что этот объект умеет бегать), то что мы будем использовать? Виртуальные методы (честное слово, не представляю как) или все-таки для этого используются интерфейсы?
я понял, о чем вы говорите. Постараюсь объяснить.
Абстракцию "интерфейс" можно рассматривать как частный случай абстракции "виртуальный метод". Во многих объектно-ориентированных языках абстрактные методы являются частным случаем виртуальных методов, при этом абстрактный метод — это виртуальный метод без реализации, который требует обязательного переопределения в классах-потомках. Интерфейсы же по сути содержат только абстрактные методы (без реализации), задавая контракт, который должны реализовывать классы.
Не все языки имеют конструкции языка - интерфейсы.
В языке C++ абстракция интерфейса поддерживается через использование абстрактных классов с чисто виртуальными методами. В C++ нет отдельного ключевого слова для интерфейсов, как в некоторых других языках, но любой абстрактный класс с одним или более чисто виртуальными методами (объявленными с = 0) фактически играет роль интерфейса.
virtual void methodName() = 0;
Какой-то не полный пример. Команд должно быть несколько? Иначе зачем тогда оно вообще нужно. И если их будет несколько, не факт что получится реализовать общий интерфейс? А будет ещё хуже, когда например уже существующий интерфейс команды не получится расширить из-за того что в методе execute не нужны такие параметры
автор не адаптировал паттерн Command, а заменил команду на сервис
Проблема в том, что нет такого паттерна как Service.
Когда мы говорим о Service имеется ввиду некое общее определение:
Service — обычно структурный или фасадный паттерн, инкапсулирующий определённую бизнес-логику или предоставляющий набор операций (сервис), которые используют другие компоненты приложения.
И да, после модификации Command вышел за границы своей изначальной ответсвенности, и по сути это уже не Command.
В объектно-ориентированном программировании шаблон проектирования Команда является поведенческим шаблоном, в котором объект используется для инкапсуляции всей информации, необходимой для выполнения действия или вызова события в более позднее время. Эта информация включает в себя имя метода, объект, который является владельцем метода и значения параметров метода.
Но моя статья про практику, а не теорию. Возможно стоит именовать предложенный подход как то иначе?
PS Возможно предлагаемый подход больше напоминает Function Object Pattern, который не столь широко известен.
Проблема в том, что нет такого паттерна как Service.
Возможно стоит именовать предложенный подход как то иначе?
Ну как это нет. Он называется "Бизнес-логика в сервисах". Только это не паттерн, а архитектурный подход.
Необязательно делать отдельный сервис на каждое действие, тогда можно переиспользовать зависимости и private-методы в реализации разных связанных бизнес-действий.
Можно было обойтись "визитером"
Мне кажется, в вашем подходе теряется главное - абстрактность команд.
Execute идет без параметров не потому, что так модно, а потому, что все команды поддерживают один интерфейс, разнородные команды можно складывать в очередь, выполнять позже, сериализовать и десериализовать, передавать по сети и т.д.
Ну и потребителю команды должно быть плевать, о чем она.
Команды - это функции с ООП оберткой.
Если же она принимает явные параметры - теряется суть паттерна. Это становится просто анемичной функцией с архитектурной ООП космонавтикой.
Ваша статья примерно так и выглядит, уж извините. Фабрика для порождения команды, которой передается сервис и параметр, чтобы у сервиса вызвать параметр - выглядит как FooBar ООП едишн и непонятно, что решает.
А между тем сама проблема довольно сложная и важная - задача разделения данных для выполнения (фактически параметров команды) и зависимостей довольно важна, ровно для того, чтобы не таскать везде зависимости (что само по себе противоположно тому, зачем нужны команды).
Простой пример, который это реализует, может выглядеть так:
- Это пример для команд, которые сохраняются в очередь и передаются по сети, чтобы клиент и сервер одинаково их применяли.
- Команда делается из нескольких классов, собственно команды (класс определенного типа с данными) и ее исполнителя - специализированного класса, который содержит логику и набор зависимостей, которые нужны команде для работы.
- Исполнители создаются лениво и 1 раз для 1 типа комманды
- При старте команды сохраняется собственно класс данных (он же сериализуется и может быть отложен). При необходимости выполнения исполнитель команд находит/создает в списке специализированный исполнитель и отдает ему команду.
Это же можно сделать шиворот-навыворот - команда описывается с Execute, который требует некоторый тип Context класса с зависимостями.
Если кому то интересно, могу накидать код, все ж с рабочего проекта копипастить не очень (да и кривенький)
Я за звание специалиста не лезу, но
1)
var createUserCommand = new CreateUser(userProvider, userName);
Вроде все про DI, но почему команда создания юзера явно вызывается? Скорее всего там через CreateUserCommandFactory вызов должен идти
2) Я так и не увидил, так в чем собственно преимущество использования комманд, если биндинги
явные CreateUser
и что мешает сделать биндинг .addService<ICreateUser, CreateUser>() ибо описанное в статье ничем не отличается явным указанием сервиса в зависимястях ибо ничем от обычного сервиса он не отличается...
Адаптированный паттерн Command с использованием Dependency Injection