[Из песочницы] Простой генератор DGML-файла графа переходов машины состояний

Допустим, есть проект WPF/MVVM, в котором необходимо реализовать шаблон State Machine, позволяющий управлять поведением объекта (в данном случае, ViewModel) в зависимости от того состояния, в котором он находится. При этом необходимо получить простую реализацию этого шаблона без использования Windows Workflow Foundation, которая включала бы в себя классы состояний, класс реализующий логику переходов и таблицу переходов. И наряду с вопросами реализации этого шаблона стоит задача реализации инструмента, автоматизирующего процесс построения диаграммы состояний на основе таблицы переходов. При этом граф, построенный с помощью этого инструмента, должен отвечать следующим требованиям:

  • граф должен обладать понятной и упорядоченной визуальной структурой (ручное упорядочивание вершин и связей графа должно быть сведено к минимуму);
  • файл графа должен быть включен в проект и, соответственно, в систему контроля версий;
  • вершина графа должны обладать кликабельной ссылкой на файл, в котором реализовано состояние;
  • должна быть реализована возможность задавать стили к вершинам графа.


Так, если про реализацию паттерна машины состояний в контексте проекта WPF/MVVM есть достаточно материала, то для решения второй задачи – реализации генератора графа переходов – очевидного решения не нашлось. Но при анализе материала на эту тему я наткнулся на эту статью, которая меня и натолкнула на решение. Так, в этой статье автор вручную формирует граф состояний с помощью инструмента Visual Studio, а именно визуального редактора DGML-файлов (Direct Graph Markup Language), и далее, на основе полученного графа, программно формирует таблицу переходов машины состояний.

DGML-файл (файл ориентированного графа) имеет XML представление, структура которого отлично описана в MSDN. Так, программно редактируя XML представление можно изменить визуальное представление графа. Таким образом, был выбран инструмент визуализации графа, осталось реализовать генератор, который на основе имеющейся таблицы переходов формировал бы XML представление DGML-файла.

Так было принято решение добавить DGML-файл в решение проекта и реализовать генератор графа в тестовом методе:

        [TestMethod]
        public void ClientStateMachineTest()
        {
            // Экземпляр машины состояний ClientStateMachine
            var clientStateMachine = new ClientStateMachine();

            var xmlDoc = new XmlDocument();

            // Относительный путь до DGML-файла, включенного в решение проекта
            const string fileDgml = @"..\..\SM\Test\ClientStateMachineGraph.dgml";

            xmlDoc.Load(fileDgml);

            var nodeLinks = xmlDoc.SelectSingleNode("/*[local-name()='DirectedGraph']/*[local-name()='Links']");
            var nodes = xmlDoc.SelectSingleNode("/*[local-name()='DirectedGraph']/*[local-name()='Nodes']");

            if (nodes != null)
            {
                nodes.RemoveAll();
                foreach (var state in clientStateMachine.StatesCollection)
                {
                    var newNode = xmlDoc.CreateNode(XmlNodeType.Element, "Node", "http://schemas.microsoft.com/vs/2009/dgml");

                    var id = xmlDoc.CreateAttribute("Id");
                    id.Value = state.GetType().Name;

                    var reference = xmlDoc.CreateAttribute("Reference");
                    reference.Value = string.Format(@"..\..\SM\States\{0}.cs", state.GetType().Name);

                    var background = xmlDoc.CreateAttribute("Background");
                    background.Value = state.Background.Name;

                    if (newNode.Attributes != null)
                    {
                        newNode.Attributes.Append(id);
                        newNode.Attributes.Append(background);
                        newNode.Attributes.Append(reference);
                    }
                    nodes.AppendChild(newNode);
                }
            }

            if (nodeLinks != null)
            {
                nodeLinks.RemoveAll();
                foreach (var tr in clientStateMachine.Transitions)
                {
                    var newLink = xmlDoc.CreateNode(XmlNodeType.Element, "Link", "http://schemas.microsoft.com/vs/2009/dgml");

                    var source = xmlDoc.CreateAttribute("Source");
                    source.Value = (tr.Value.InitialState).GetType().Name;

                    var target = xmlDoc.CreateAttribute("Target");
                    target.Value = tr.Value.FinalState.GetType().Name;

                    if (newLink.Attributes != null)
                    {
                        newLink.Attributes.Append(source);
                        newLink.Attributes.Append(target);
                    }
                    nodeLinks.AppendChild(newLink);
                }
            }
            xmlDoc.Save(fileDgml);                
        }


В начале метода на основе относительного пути к DGML-файлу проекта загружается XML-документ, из которого извлекаются XML-узел Links, содержащий ориентированные связи графа Link, и XML-узел Nodes, содержащий вершины графа Node.

Далее, на основе коллекции состояний clientStateMachine.StatesCollection формируются вершины графа, у которых устанавливаются ссылки на файлы состояний и цвет фона.

Затем, на основе каждого перехода из таблицы переходов clientStateMachine.Transitions, имеющего начальное InitialState и конечное FinalState состояния, формируется направленное ребро графа путем добавления соответствующих атрибутов Source и Target в XML-элемент Link.

Результат выполнения этого тестового метода представлен на рисунке ниже.

image

В заключение, хочу отметить, что:

  • наглядная структура графа, без наложения или пересечения вершин и связей, была получена автоматически с помощью конструктора макета графа, что является отличным преимуществом данного инструмента Visual Studio;
  • перейти по ссылке к файлу состояния можно из контекстного меню вершины графа;
  • представленный генератор можно легко адаптировать под любую машину состояний, имеющую таблицу переходов.


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

© Habrahabr.ru