Допустим, есть проект 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.
Результат выполнения этого тестового метода представлен на рисунке ниже.
В заключение, хочу отметить, что:
- наглядная структура графа, без наложения или пересечения вершин и связей, была получена автоматически с помощью конструктора макета графа, что является отличным преимуществом данного инструмента Visual Studio;
- перейти по ссылке к файлу состояния можно из контекстного меню вершины графа;
- представленный генератор можно легко адаптировать под любую машину состояний, имеющую таблицу переходов.
Таким образом, представлена простая, но эффективная реализация генератора ориентированного графа в тестовом методе, выполнение которого позволяет получить актуальную версию диаграммы состояний.
ссылка на оригинал статьи http://habrahabr.ru/post/269983/
Добавить комментарий