Шаблон диаграммы состояний предназначен специально для удаления операторов switch, так что это звучит как ужасное злоупотребление. Кроме того, состояния должны изменяться только при асинхронных событиях. Если вы обрабатываете событие и изменяете его через несколько состояний (или для цикла и т. Д.), То это также ужасное злоупотребление шаблоном.
Я бы начал с этих двух пунктов, так как они решат большинство ваших проблем параллелизма, просто решив их. То, что вам нужно определить, это:
- Каковы ваши внешние, асинхронные события для системы? Это единственные вещи, которые должны определять переходы состояний, а не вещи, которые происходят во время обработки событий. Событие может вызвать 0 или 1 переходы состояний. Получив список этих переходов состояний, вы можете восстановить действительные состояния вашей системы. Если вам известны диаграммы состояния UML, это было бы идеальное время для того, чтобы набросать одну из них в программе построения диаграмм не только для себя (хотя это вам очень поможет), но и для всех, кто в будущем должен вернуться к проект. Как вы узнали, это происходит.
- Теперь, когда вы знаете, что такое состояния, перечислите в коде состояния, которых быть не должно. Обычно это означает, что что-то может быть «функционально разложено». Вместо объекта состояния для каждого из них, вероятно, все, что нужно, - это отдельная функция. Это сократит значительную нагрузку на объекты состояния и приведет к огромной очистке кода.
- Теперь пришло время заняться этими ужасными заявлениями переключателей, которые вы упомянули. Если они действительно основаны на состоянии, вам не нужно вообще. Вместо этого вы сможете напрямую вызывать конечный автомат.
Что-то вроде:
myStateMachine->myEvent();
и он должен работать без какого-либо переключателя. Но обратите внимание, это может иметь место даже для некоторых объектов, которые не работают с асинхронными событиями. Это также указывает на то, где вы можете просто использовать наследование, чтобы получить тот же эффект. Если у вас есть:
switch (someTypeIdentifier)
{
case type1:
doSomething();
break;
case type2:
doSomethingElse();
break;
}
обычно правильный метод ООП - создать два фактических типа Type1, Type2, оба получаются из abstract base TypeBase, с виртуальным методом doSomething (), который делает то, что вам нужно. Это полезно потому, что это означает, что вы можете «закрыть» обработку (в смысле открытого / закрытого принципа) и при этом расширять функциональность, добавляя новые производные типы по мере необходимости (оставляя его открытым для расширения). Это сохраняет ошибки, как сумасшедшие, потому что позволяет разработчикам получить доступ к этим операторам switch, которые могут быть довольно уродливыми и запутанными, вместо этого инкапсулируя каждое отдельное поведение в отдельных классах.
4 - Теперь посмотрите, чтобы исправить проблемы с нитями. Определите все объекты, используемые из нескольких потоков. Сделай список. Теперь, как они используются? Некоторые из них всегда используются вместе? Начните делать группы. Цель здесь состоит в том, чтобы найти уровень инкапсуляции, который лучше всего подходит для этих объектов, разделить объекты на отдельные классы, которые управляют их собственной синхронизацией, выяснить атомарный уровень фактических «транзакций» для объектов и создать методы классов, которые разоблачить эти значимые транзакции, скрытые за кулисами с соответствующими мьютексами, условными переменными и т. д.
Возможно, вы говорите: «Это звучит как большая работа! Почему все это вместо того, чтобы просто писать все над собой?» Хороший вопрос! :) Причина на самом деле проста: если вы собираетесь делать все самостоятельно, это те шаги, которые вы должны делать в любом случае. Вы должны определить свои состояния, свой динамический полиморфизм и получить контроль над многопоточными транзакциями. Но если вы начнете с существующего кода, у вас также будут все те невысказанные бизнес-правила, которые никогда не были документированы и могут привести к неожиданным ошибкам. Вам не нужно доводить все до конца - если вы подозреваете, что это ошибка, обсудите логику с людьми, которые работали с системой в прошлом (если есть), QA или с кем-либо, кто мог бы идентифицировать ошибки, и посмотрите, действительно ли это должны быть перенесены. Но вам нужно на самом деле оценить, какие ошибки есть, так или иначе, или вы не можете кодировать то, что действительно требовало кодирования.
В конце концов, это ручной процесс, который является частью разработки программного обеспечения. Существуют инструменты CASE, которые могут помочь составить диаграммы состояний и даже опубликовать их в коде, есть инструменты рефакторинга, подобные тем, которые встречаются во многих IDE, которые могут помочь перемещать код между функциями и классами, и аналогичные инструменты, которые могут помочь определить потребности потоков , Тем не менее, эти вещи не должны быть подобраны для одного проекта. Их нужно изучать на протяжении всей вашей карьеры, подбирая их и изучая их глубже за годы работы, поскольку они являются частью работы инженера-программиста. Они не делают это для вас. Вам все еще нужно знать, почему и как, и они просто помогают сделать это более эффективно.