Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

State pattern

539  
  moose коренной житель19.01.20 18:15
19.01.20 18:15 

привычная модель: абстрактный класс state с производными по штуке на state. каждый конкретный класс/объект обрабатывает поступающие/происходящие message/event по-своему.


другая, непривычная модель: абстрактный message/event обработчик, (можно обозвать его handler/processor, т.е. обработчик событий/сообщений), с производными конкретными обработчиками, по штуке на тип события/сообщения, каждый из которых обрабатывает событие в зависимости от текущего состояния.


вроде бы просто меняем в таблице столбцы со строчками. в некоторых случаях что будет строка, а что столбец продиктовано какой-то логикой, а иногда разницы нет.


вернемся к заявленной теме. я ни разу не встречал, чтобы рассматривался второй вариант, с обработчиками. и туплю, не могу понять почему: или просто уже все договорились ездить по левой/правой стороне дороги, или при каком-то варианте происходит меньше аварий?


говорит ли что-нибудь против второго варианта кроме привычки? в конце-концов мы приземляемся в чей-нибудь метод, определяемый текущим состоянием и обрабатываемым соообщением/событием. будут они сгруппированы по файлам/классам по признаку состояния или типа события - дело привычки по-моему. или все-таки что-то удобнее?

#1 
AlexNek патриот19.01.20 21:24
AlexNek
NEW 19.01.20 21:24 
в ответ moose 19.01.20 18:15

Ничего не понял, сорри смущ

С первым понятно, а что со вторым???

https://www.dotnettricks.com/learn/designpatterns/state-de...


#2 
MrSanders коренной житель19.01.20 22:20
NEW 19.01.20 22:20 
в ответ moose 19.01.20 18:15

Вариант с состояниями во-первых привычнее и понятнее, потому как отображает привычную диаграмму состояний. Проще использовать, меньше ошибок сделаешь.

Любой другой вариант реализации машины состояний надо обосновать. Может в каком-то конкретном случае удобнее будет сделать как во втором варианте описано. Например, если у нас 2 разных события но 100 состояний с минимальными отличаями, я бы постарался обойтись без 100 подклассов. Но скорее переопределил бы а что у нас состоянием называется.


С обработчиками проблема будет с пониманием "что это такое". Обычно если мы говорим об "event-based" то у нас на каждое событие может быть много обработчиков, никакой определённой очерёдности обработки событий, и по максимум распараллеливание. В вашем варианте у каждого события обработчик один. Если обработчиков много, то как им всем переключать состояние? Тоже событием? Тогда надо как-то синхронизировать очередь сообщений, не выдавать следующие события, пока все обработчики не переключились в нужное состояние, а то один обработчик в состояние Б переключился и уже следующее событие обрабатывает, а второй всё ещё в состоянии А висит. Сломаем весь смысл событий.

#3 
  moose коренной житель19.01.20 23:40
NEW 19.01.20 23:40 
в ответ MrSanders 19.01.20 22:20

что привычную - это ясно. насчет 100 состояний - то это как с таблицей. здесь естественно читать таблицу с тремя колонками и 100 строками, чем наоборот. но с таблицей все зависит от конкретностей скорее, чем от привычек.


что касается "переключился-не успел переключиться", то объект у нас - один, и обрабатывает все сообщения, выбирая их из одной очереди. выбрал сообщение - вызвал обработчик соотв. типа. сообщив ему текущее состояние. обработчик обработал сообщение и вернул новое состояние (возможно, то же самое). фактически вся кухня закручена для того, чтобы при поступлении сообщения (наступления события. но не путать с другими patterns, где на одно событие может быть множество подписчиков! возможно, безусловно, на одно и то же сообщение/событие реагировать нескольким state machine, но мы не об этом) его обработал какой-то метод, предназначенный для обработки данного сообщения в данном состоянии. в принципе, можно это в двух вложенных switch конструкциях обработать. принципиальной разницы, какой свитч будет внешним, какой внутренним, нет. можем, в принципе, увеличить "мерность" этого пэттерна, добавив еще что-нибудь. день недели например. извините, в вашем посте я не увидел других весомых аргументов, кроме "отображает привычную диаграмму состояний". это, конечно, аргумент, но неочевидно, почему "Проще использовать, меньше ошибок сделаешь.". привычнешь за пять минут, и будет так же удобно, как и раньше.


представьте себе сравнительную таблицу параметров какого-нибудь продукта. количество параметров и продуктов примерно одинаково. скажем, десяток. приходилось видеть и строка==продукт, и строка==параметр. никакого привыкания, оба варианта подходят. лишь бы продукты и параметры были выбраны правильно : )

#4 
  moose коренной житель19.01.20 23:42
NEW 19.01.20 23:42 
в ответ AlexNek 19.01.20 21:24
Ничего не понял, сорри смущ

ничего страшного : )

С первым понятно

а все-таки? ничего не перепутали?

#5 
AlexNek патриот20.01.20 13:50
AlexNek
NEW 20.01.20 13:50 
в ответ moose 19.01.20 23:42
а все-таки? ничего не перепутали?

Как написали так и взял. Лучше бы проблему описали, чем названиями жонглировать. смущ Очень часто GOF даёт больше чем требуется.

#6 
Программист коренной житель21.01.20 09:11
NEW 21.01.20 09:11 
в ответ moose 19.01.20 18:15

Если я тебя правильно понял, то на мой взгляд, нет никакой разницы. Более того, я даже не вижу принципиально иного взгляда.


Если в одном (классическом) случае у тебя будет как-то так:

interface IState
{
   void Render ();
   void Publish ();
}


то в другом (непривычном) будет как-то так:

interface IState
{
   void OnEvent (RenderEvent e);
   void OnEvent (PublishEvent e);
}
#7 
  moose коренной житель21.01.20 11:16
NEW 21.01.20 11:16 
в ответ Программист 21.01.20 09:11, Последний раз изменено 21.01.20 11:17 (moose)

нет. в "классическом" будет базовый State класс, от которого производятся классы по одному на конкретное состояние. машина выбирает сообщение/событие из очереди и отправляет его текущему состоянию, тот знает, как поступить с каждым типом сообщения.


в "альтернативном" будет базовый Event(Handler) класс (а не State в обоих случаях, как у вас), от которого производятся классы по одному на конкретный тип события. машина выбирает сообщение из очереди, как и в классиме, но отправляет его обработчику данного события, передавая ему значение текущего состояния.


в принципе обработкой занимается тот же метод, но находим его в одном случае сперва по икс-координате, затем - по игрек, в другом случае - наоборот. попадаем в ту же точку.


сделал прототипы обоих вариантов, классика выглядит немного проще. разница незначительна, и все что составляет "разницу" находится в автоматически генерируемой части кода, но все-таки остановлюсь на классическом. хотя альтернативный тоже вполне работоспособный. принципиальных препятствий не встретил.

#8 
MrSanders коренной житель22.01.20 13:53
NEW 22.01.20 13:53 
в ответ moose 19.01.20 23:40
Проще использовать, меньше ошибок сделаешь.". привычнешь за пять минут, и будет так же удобно, как и раньше.

Ну не 5 минут, но да, конечно, со временем привыкнешь. Проблема в том, что любой разработчик, который потом будет в этом коде ковырятся, тоже должен будет привыкать. Это - стоимость нестандартного решения. Если плюсы перевесят, можно делать и нестандартно, почему бы и нет.

приходилось видеть и строка==продукт, и строка==параметр. никакого привыкания, оба варианта подходят.

А вот не совсем. Оба варианта подходят для разного. Ожидается что столбцы или вообще не изменяются или меняются редко (потому как дорого процесс, всю таблицу менять), а строки постоянно добавляются/удаляются/меняются.


В общем из того что я услышал технических причин не делать во второму варианту я не вижу - будет работать. Только не надо слово "event" использовать, чтобы не смущать.

P.S. Это как в чертеже дома оси местами поменять и нарисовать дом лежащим на боку. Построить можно, но нужно привыкнуть.

#9 
  moose коренной житель22.01.20 16:29
NEW 22.01.20 16:29 
в ответ MrSanders 22.01.20 13:53, Последний раз изменено 22.01.20 17:04 (moose)
Ну не 5 минут, но да, конечно, со временем привыкнешь. Проблема в том, что любой разработчик, который потом будет в этом коде ковырятся, тоже должен будет привыкать.

даже меньше пяти минут. в любом случае для новичка оба варианта потребуют одинакового времени на поиск нужного обработчика. поставил брэйкпоинт на выборке из очереди и проследил 2-3 шага, как "оно" попадает в нужный обработчик. все.


А вот не совсем. Оба варианта подходят для разного. Ожидается что столбцы или вообще не изменяются или меняются редко (потому как дорого процесс, всю таблицу менять), а строки постоянно добавляются/удаляются/меняются.

можете сравнить:

http://bigit.karikaturize.com/solar-panel-comparison-chart...

https://www.edrawsoft.com/linuxdiagram/comparison-chart-so...

в одном случае продукт == столбец, а строка == фича, в другом случае наоборот. и добавляться могут как продукты, так и фичи. в данном случае просто чего больше, то должно быть строчками, хотя не принципиально. принцип portrait/landscape.

или гоголю задать top 10 comparison table -> Images, встретите и таких, и других во множестве.


мне почему-то только сейчас стало понятно, что я абсолютно некорректно вопрос задал. я спрашивал о State pattern. и он ПО ОПРЕДЕЛЕНИЮ устроен по первому варианту. мой второй вариант скорее подходит под Strategy pattern, или можно еще какое-нибудь пока не замусоленное название подобрать, чтоб еще сильней жизнь усложнить и запутать : )

#10 
MrSanders коренной житель22.01.20 18:04
NEW 22.01.20 18:04 
в ответ moose 22.01.20 16:29
в одном случае продукт == столбец, а строка == фича, в другом случае наоборот. и добавляться могут как продукты, так и фичи.

Я просто на автомате "таблица" как "таблица БД" понял. Да, просто напечатанную таблицу как хочешь крути. Хотя тут тоже наверняка свои заморочки. Я уверен что какой-нибудь маркетолог много расскажет об огромной разнице что ставить строками а что столбцами.

#11 
Бесконечный цикл гость22.01.20 21:22
NEW 22.01.20 21:22 
в ответ moose 19.01.20 18:15

Какой-то поток сознания. Походу у Мура научился проблемы описывать.


Отделяем мух от котлет:

1) ООП нужно только если State это действительно (общее) состояние, которое надо соответственно моделировать типа счетчика событий. Эта задача не связана с другими.

2) Далее идет диспетчер, который также не связан с другими задачами.

3) И потом обработчики которые тоже реализуются независимо (либо как методы, либо как функции).


Ну и еще пару мыслей по поводу как это реализовать:


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


- Если State и Event оба просто данные, которые надо обрабатывать, тогда очевидно, что все симметрично. Состояния здесь нет.


- Если количество значений (State или Event) небольшое, то можно для каждого определить свой обработчик со своим именем, например, handler_Event25. Предпоалагается, что будущим поколениям не надо долго объяснять что делать.


- Диспетчер можно реализовать явно (switch),что хорошо (поскльку понятно другим), либо привинтить ОПП, где вызов виртуальных функций это и есть диспетчер, что плохо, поскольку люди потом должны догадываться, а нафига тут ООП с иерархией классов (подклассы будут иметь имена типа Subclass_State25, но все обработчики с одним именем).


- Если значения State и Event известны заранее, то можно все закодировать в код. Если нет, то можно динамически находить или подсасывать извне нужный код (и даже компилировать на ходу).


#12 
  moose коренной житель22.01.20 21:33
NEW 22.01.20 21:33 
в ответ MrSanders 22.01.20 18:04, Последний раз изменено 22.01.20 21:34 (moose)

смело передаю эстафету потока сознания в ваши руки! уверен, не подведете! завтра даже прочту до конца. ; )

#13 
AlexNek патриот23.01.20 20:38
AlexNek
NEW 23.01.20 20:38 
в ответ Бесконечный цикл 22.01.20 21:22
Диспетчер можно реализовать явно (switch),что хорошо

То бишь SOLID - это полное Г.?смущ


#14 
MrSanders коренной житель24.01.20 09:41
NEW 24.01.20 09:41 
в ответ AlexNek 23.01.20 20:38

Ну, open-close все же спокойно можно нарушать сознательно. Чтобы защитить свой код, например. Или упростить понимание кода, усложнив расширяемость, если мы считаем что тут расширение маловероятно. Я даже больше скажу - по-моему нет ни одного прокта, в котором не был бы где-нибудь нарушен open-close. Потому что YAGNI.

#15 
AlexNek патриот24.01.20 13:17
AlexNek
NEW 24.01.20 13:17 
в ответ MrSanders 24.01.20 09:41
можно нарушать сознательно

сознательно, но ведь же не всегда спок

#16