Назначение и применение паттерна Strategy и StateMachine.
Автор: Павлов М.Ю., Боднар А.В.
Источник:Донбасс будущего глазами молодых ученых : сб. материалов науч.-техн.
конф. для студ., асп. и мол. уч. / Ред. кол.: Руденко М. П. (пред.); Захарова В.
В., Валицкая С. В. [и др.]; отв. ред. М. П. Руденко. – Донецк : ДонНТУ, 2024. - 451 с.
ИУСМКМ-2024
Аннотация
Павлов М.Ю., Боднар А.В. Назначение и применение паттерна Strategy и StateMachine. . В работе рассматриваются один из базовых паттернов поведения strategy и одно из его проявлений – stateMachine. Выделяется основа паттерна strategy, его происхождение и основное назначение. Паттерн является инструментом, который произошел из более простого метода реализации в результате структурированного развития системы. В качестве примера приводятся математические формулы, выведенные из простых действий.
Введение
Решение задач – сложная и глубокая тема. Поиск решения может охватывать как огромные области, так и быть узконаправленным. В процессе поиска решений обнаруживается типовой алгоритм или определенный шаблон, следование которому позволяет устранить проблему наиболее эффективным образом. В программировании такие шаблоны называются «паттерны проектирования».
Сами паттерны можно рассматривать как математические формулы, которые используются для преобразования, упрощения или приведения выражений. Паттерны раскрывают возможности манипуляции данными и обладают огромным потенциалом в поиске решений.
Математические преобразования хорошо отражают суть паттернов: и то, и другое можно охарактеризовать как упрощение какого-либо действия или правило, выведенное из простых, базовых конструкций. В качестве примера можно привести сокращенное умножение и описать его как процесс раскрытия скобок, повторяемый до тех пор, пока не будет получено решение. В основе паттерна всегда лежит простое действие, которое возможно повторять для выполнения более сложных действий.
Подобно тому, как в структурированной науке математике можно систематизировать определенные действия, тем самым приравнивая их к паттернам, можно описать в паттернах и то, что обладает менее системным характером. Основной темой доклада является моделирование. Так, к примеру, социальную ситуацию, где человек ведет себя по разному в зависимости от обстоятельств (на работе он более сдержан, а с друзьями открыт) можно описать через влияние внешних факторов. Эти множественные факторы –элементы, каждый из которых обладает своим свойством. Их совокупность и определяет поведение конкретного индивида.
Рассматривая вышеописанную модель с точки зрения программирования, можно найти несколько способов решения. Один из них состоит в том, что каждый выбор человека вести себя определенным образом можно выделить в отдельный класс – класс состояния, который и будет описывать его поведение. В другом способе возможно задать поведение методами, избегая создания лишних сущностей в виде классов. В таком случае каждая вариация поведения будет описываться либо отдельным методом, либо методом с параметрами, то есть с задействованием принципа полиморфизма.
Таким образом, есть два пути: использовать класс, описывающий состояние, либо использовать методы, которые делают то же самое. Внутри созданного класса можно будет обрабатывать события, зависящие от текущего состояния объекта. Использование методов также допускает обработку состояний объекта, но с одним отличием – с атрибутами можно взаимодействовать напрямую. Здесь возникает спорная ситуация: либо использовать один перегруженный метод, либо множество методов для каждого состояния.
Оба варианта используют разный подход к реализации основного функционала, который может хорошо выполняться в одних условиях и плохо – в других. Поэтому выбор реализации алгоритмов зависит от конкретной задачи. Также немаловажную роль играет архитектура приложения, под которую подстраивается рассматриваемая ситуация.
При таком построении алгоритмов возникает вопрос об их заменимости, дальнейшем развитии приложения и необходимости реализации конкретного способа. Если для описания поведения достаточно одной функции, то разумнее написать один метод. Проще всего использовать для этого делегат, то есть переменную, имеющую тип ссылки на определенную функцию. Если же появляется необходимость во множественном использовании, дополнительных параметрах и в вариативности поведения, куда лучше обзавестись собственными классами.
Теперь же рассмотрим решение проблемы более глобально. В этом случае можно ввести некое правило, не зависящее от конкретного класса, в котором оно реализуется. Скорее, оно является описанием поведения, которое может использоваться по своему назначению. Тем самым мы приходим к приведению решения в определенный шаблон или паттерн.
Теперь, имея понимание, что из себя представляет конкретный паттерн, а именно Strategy и StateMachine (далее – стратегия и машина состояний), возникает вопрос, почему в данном случае мы рассматриваем именно два паттерна. Всё просто: на самом деле машина состояний является развитием паттерна стратегия, из-за чего имеет некоторые отличия и важные особенности в реализации, а также показывает важные аспекты стратегии.
Главное свойство паттернов – оптимальное или приближенное к оптимальному решение, но лишь в необходимых условиях. В ином случае, паттерн, скорее, будет усложнять структуру и алгоритм программного обеспечения, вызывая различные путаницы в использовании и расширении.
Основа работы паттернов
Часто при реализации программного кода специалист неосознанно использует определенные паттерны проектирования. Это будет продемонстрировано на примере паттерна «стратегия».
В основе работы паттерна лежит определение семейства определенной группы с взаимозаменяемыми алгоритмами.
Если рассматривать паттерн подробнее, то в его основе лежит принцип создания одного интерфейса, на основе которого реализуются разные алгоритмы в дочерних классах, тем самым позволяя использовать тип данных родителя, а функционал – дочерних объектов.
Тем не менее, использовать паттерн можно и для одной реализации, то есть одного алгоритма. Это необходимо для выделения алгоритма и обеспечения его важности в коде.
Важной частью при реализации одного алгоритма является использование делегата вместо выделения одного интерфейса, если мы говорим о реализации в платформе .NET.
Одной из особенностей данного паттерна является специфика: как бы ни казалось, что подобный паттерн можно обобщить и использовать в виде библиотечного кода, но это в корне неверное решение. Использование подобного пути наталкивает на усложнение посредством добавления лишних сущностей и ненужных реализаций, что в корне ломает понимание и фундамент использования паттерна.
В процессе обсуждения паттерна возникает вопрос «А зачем вообще выделять реализацию алгоритма в стратегии, если уже есть несколько алгоритмов, реализованных в методах или определяющих функционал класса?». Это здравый вопрос.
Но самой важной особенностью является не сама реализация алгоритма, а возможность спрятать его, тем самым обеспечив его заменяемость во время исполнения программного кода. Следовательно, стратегия обеспечивает возможность расширения системы в плоскости алгоритма использования, а именно – контекст класса не знает, какой вариант стратегии он будет использовать.
Теперь рассмотрим применимость стратегии. Первое, что стоит сказать – не следует использовать стратегию просто так. Наследование всегда добавляет дополнительную сложность программе, как минимум как элемент усложнения общей структуры. Но тут же возникает проблема увеличения количества сущностей, что напрямую идет вразрез с принципом замещения Лисков. Из чего можно сделать вывод, что использование подобных структур необходимо только тогда, когда появляется требуется замена поведения во время исполнения структурного кода.
Теперь, имея понимание, как работает основной паттерн, рассмотрим более подробно машину состояний.
В основе паттерна лежит, как ни странно, машина Тьюринга, ведь именно она стала первым примером использования состояний и путей переходов между ними.
Важнейшей отличительной особенностью является именно переход в другое состояние, что влечет за собой изменения поведения объекта при определенных условиях. Что извне выглядит так, словно один из классов был заменен на другой путем преобразования алгоритма. В общем, многие особенности машины состояний очень схожи со стратегией, в особенности проблемы увеличения сущностей и функционала. Но тут появляется другая проблема. Если же в случае со стратегией мы чаще обращаем внимание на сложность выполняющегося алгоритма, то в случае с машиной состояний мы смотрим на сложность конечного автомата или состояния.
Это не только упрощает код, делая его более читаемым, но и обеспечивает контроль над условиями и моментами перехода. Таким образом, машина состояний позволяет избежать множества ошибок и повысить устойчивость программы к изменениям.
Как правило, машину состояний используют для изменения работы флагов. В пример можно привести работу сайта и всплывающего меню с вкладками. Допустим, у нас есть подобное окно: при наведении или щелчке мышью, данное окно проигрывает анимацию открытия. Теперь нам нужно знать, открыто ли окно, но мы можем воспользоваться флагом, который, собственно, и будет показывать его состояние на текущий момент. Теперь, при частых кликах возникает стандартная проблема: у нас начинает мигать окно, поскольку анимация не успевает выполниться. Тут, казалось бы, мы можем воспользоваться тем же примером и использовать флаг. Но в таком случае мы начинаем множить сущности, и ответственность за понимание состояния полностью перетекает на этот класс. Здесь и появляется возможность использовать машину состояний.
Машина состояний – не такая сложная конструкция. Общая механика у неё такая же, как и в стратегии, что обуславливает некоторые из её особенностей. В нашем случае мы выносим несколько состояний, которые напрямую заменяют собой флаги. То есть код выполнения больше не будет зависеть от них. При работе определенного состояния выполняется переход в другое: например, мы открываем всплывающее меню, переходя из стандартного состояния в состояние анимации, а после её выполнения – в состояние открытое окно. Тем самым машина состояний превращается в интересную машину, которую можно использовать для реализации и подчинения различных структур, избавляясь от дополнительных вложений благодаря использованию конструкций.
Выводы
В заключение стоит сказать, что паттерны проектирования – очень сильный инструмент, который может помочь в решении многих проблем, в том числе решить вопросы архитектуры, систематизировать вопрос будущего расширения программы и использоваться как способ решения упрощения вложенной логики, замены флагов и др.
В нашем случае, были рассмотрены два паттерна, которые имеют огромную область применения, тем не менее, они и обладают негативным аспектом в виде ненужного усложнения кода при неправильном понимании основ использования, что позволяет сделать следующий вывод: инструменты нужно применять там, где они необходимы.
Таким образом, важно помнить, что даже самые полезные паттерны проектирования не являются универсальным решением для всех задач. Применение каждого паттерна требует глубокого анализа и понимания контекста, в котором он используется, чтобы избежать излишней сложности, поддерживая баланс между гибкостью кода и его читабельностью.
Список использованной литературы
1. Хокинг Д. Unity - в действии. Мультиплатформенная разработка на C# // - 2-е издание - СПб : Питер, 2016. - 336 с.
2. Unity User Manual 2022.3 (LTS) // [Электронный ресурс]. - Режим доступа: https://docs.unity3d.com/Manual/UnityManual.html
3. unity-coding-standards // [Электронный ресурс]. - Режим доступа: https://leotgo.github.io/unity-coding-standards/
4. Роберт М. Чистый код: создание, анализ и рефакторинг Библиотека программиста. // перевод с английского - СПб.: Питер, 2013. - 464 с
5. Вирт Н. Программирование на языке Modula-2 // Вирт Н. – перевод с английского – Москва: Мир 1987 – 224 с.
6. Введение в компонентно-ориентированный подход к программированию habr // [Электронный ресурс]. - Режим доступа: https://habr.com/ru/articles/243479/
7. Троелсен, Э. Язык программирования C# 7 и платформы .NET и .NET Core, 8-е изд. : Пер. с англ. / Э. Троелсен, Ф. Джепикс. СПб. : ООО «Диалектика». – 2018. – с. 1328
8. Heinman G.T. Component-based software engineering : putting the pieces together // Heinman G.T. Concill W. T. - Boston : Addison-Wesley 2001 – 818 c.