Работа конечных автоматов и пользовательского интерфейса - есть ли примеры / опыт? - PullRequest
30 голосов
/ 27 февраля 2009

Я ищу способы деактивации кода моего внешнего виджета. Было высказано предположение, что Finite State Machine - правильный способ думать о том, что я делаю. Я знаю, что парадигма конечного автомата может применяться практически к любой проблеме. Мне интересно, есть ли опытные программисты пользовательского интерфейса, которые на самом деле делают это привычкой.

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

спасибо, -Morgan

Ответы [ 10 ]

30 голосов
/ 27 февраля 2009

В настоящее время я работаю с (проприетарной) структурой, которая хорошо подходит для парадигмы UI-as-state-machine, и она может определенно уменьшить (но не устранить) проблемы со сложными и непредвиденными взаимодействиями между элементами UI.

Основным преимуществом является то, что он позволяет мыслить на более высоком уровне абстракции, с более высокой степенью детализации. Вместо того, чтобы думать «Если нажата кнопка A, то поле со списком B заблокировано, текстовое поле C очищено, а кнопка D разблокирована», вы думаете «Нажатие кнопки A переводит приложение в состояние CHECKED» - и переход в это состояние означает, что определенные вещи случается.

Я не думаю, что полезно (или даже возможно) моделировать весь пользовательский интерфейс как единый конечный автомат. Вместо этого обычно есть несколько меньших конечных автоматов, каждый из которых обрабатывает одну часть пользовательского интерфейса (состоящую из нескольких элементов управления, которые концептуально взаимодействуют и объединяются вместе), и один (может быть, больше чем один) «глобальный» конечный автомат, который обрабатывает более фундаментальные проблемы.

10 голосов
/ 27 февраля 2009

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

Мне нравится думать об интерфейсах с продолжениями. (Google это - термин достаточно конкретен, что вы получите много высококачественных хитов.)

Вместо того, чтобы мои приложения находились в различных состояниях, представленных флагами состояния и режимами, я использую продолжения для управления тем, что приложение делает дальше. Проще всего объяснить на примере. Скажем, вы хотите открыть диалоговое окно подтверждения перед отправкой электронного письма. Шаг 1 создает письмо. Шаг 2 получает подтверждение. Шаг 3 отправляет электронное письмо. Большинство инструментариев пользовательского интерфейса требуют, чтобы вы возвращали управление обратно в цикл обработки событий после каждого шага, что делает его действительно ужасным, если вы пытаетесь представить его с помощью конечного автомата. С продолжениями вы не думаете о шагах, которые навязывает вам инструментарий - это всего лишь один процесс создания и отправки электронного письма. Однако, когда процессу требуется подтверждение, вы фиксируете состояние вашего приложения в продолжении и передаете это продолжение кнопке OK в диалоговом окне подтверждения. Когда нажата OK, ваше приложение продолжает с того места, где оно было.

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

Надеюсь, это даст вам несколько новых направлений для размышлений. Я постараюсь вернуться позже с реальным кодом, чтобы показать вам, как он работает.

Обновление: вот полный пример с Qt в Ruby. Интересные части находятся в ConfirmationButton и MailButton. Я не эксперт по Qt или Ruby, поэтому буду признателен за любые улучшения, которые вы все можете предложить.

require 'Qt4'

class ConfirmationWindow < Qt::Widget
  def initialize(question, to_do_next)
    super()

    label = Qt::Label.new(question)
    ok = ConfirmationButton.new("OK")
    ok.to_do_next = to_do_next
    cancel = Qt::PushButton.new("Cancel")

    Qt::Object::connect(ok, SIGNAL('clicked()'), ok, SLOT('confirmAction()'))
    Qt::Object::connect(ok, SIGNAL('clicked()'), self, SLOT('close()'))
    Qt::Object::connect(cancel, SIGNAL('clicked()'), self, SLOT('close()'))

    box = Qt::HBoxLayout.new()
    box.addWidget(label)
    box.addWidget(ok)
    box.addWidget(cancel)

    setLayout(box)
  end
end

class ConfirmationButton < Qt::PushButton
  slots 'confirmAction()'
  attr_accessor :to_do_next
  def confirmAction()
    @to_do_next.call()
  end
end

class MailButton < Qt::PushButton
  slots 'sendMail()'
  def sendMail()
    lucky = rand().to_s()
    message = "hello world. here's your lucky number: " + lucky
    do_next = lambda {
      # Everything in this block will be delayed until the
      # the confirmation button is clicked. All the local
      # variables calculated earlier in this method will retain
      # their values.
      print "sending mail: " + message + "\n"
    }
    popup = ConfirmationWindow.new("Really send " + lucky + "?", do_next)
    popup.show()
  end
end

app = Qt::Application.new(ARGV)

window = Qt::Widget.new()
send_mail = MailButton.new("Send Mail")
quit = Qt::PushButton.new("Quit")

Qt::Object::connect(send_mail, SIGNAL('clicked()'), send_mail, SLOT('sendMail()'))
Qt::Object::connect(quit, SIGNAL('clicked()'), app, SLOT('quit()'))

box = Qt::VBoxLayout.new(window)
box.addWidget(send_mail)
box.addWidget(quit)

window.setLayout(box)
window.show()
app.exec()
9 голосов
/ 27 февраля 2009

Это не пользовательский интерфейс, который нужно моделировать как конечный автомат; это отображаемые объекты, которые могут быть полезны для моделирования в качестве конечных автоматов. Ваш UI тогда становится (упрощенно) набором обработчиков событий для изменения состояния в различных объектах.

Это изменение от:

DoSomethingToTheFooObject();  
UpdateDisplay1();  // which is the main display for the Foo object  
UpdateDisplay2();  // which has a label showing the Foo's width,
                   // which may have changed  
...  

до:

Foo.DoSomething();  

void OnFooWidthChanged() { UpdateDisplay2(); }  
void OnFooPaletteChanged() { UpdateDisplay1(); }  

Думая о том, какие изменения в отображаемых вами данных должны привести к тому, что перекрасить можно, как со стороны пользовательского интерфейса клиента, так и со стороны Foo сервера.

Если вы обнаружите, что из 100 штук пользовательского интерфейса, которые, возможно, потребуется перекрасить при изменении состояния Foo, все они должны быть перерисованы при изменении палитры, но только 10 при изменении ширины, это может означать, что события / изменения состояния Foo должен сигнализировать. Если вы обнаружите, что у вас есть большой обработчик событий OnFooStateChanged (), который проверяет ряд свойств Foo, чтобы увидеть, что изменилось, в попытке минимизировать обновления пользовательского интерфейса, он предлагает что-то о гранулярности модели событий Foo. Если вы обнаружите, что хотите написать небольшой автономный виджет пользовательского интерфейса, который можно использовать в нескольких местах вашего пользовательского интерфейса, но он должен знать, когда изменяется Foo, и вы не хотите включать весь код, который сопровождает реализация Foo, он предлагает кое-что об организации ваших данных относительно вашего пользовательского интерфейса, где вы используете классы по сравнению с интерфейсами и т. д. По сути, это заставляет вас более серьезно задумываться о том, что является вашим уровнем представления, более серьезно, чем "весь код в моих классах формы".

-PC

7 голосов
/ 01 июля 2009

Существует книга на эту тему. К сожалению, его нет в наличии, а имеющиеся в наличии редкие очень дорогие.

Constructing the User Interface with Statecharts
by Ian Horrocks, Addison-Wesley, 1998
4 голосов
/ 20 июля 2011

Мы только что говорили о Хоррокса. Построение пользовательского интерфейса с помощью диаграмм состояний , цены для вторых рук варьируются от 250 до почти 700 долларов. Наш менеджер по разработке программного обеспечения оценивает его как одну из самых важных книг, которые у него есть (к сожалению, он живет на другом конце света).

Книги Самека, посвященные диаграммам состояний, в значительной степени опираются на эту работу, хотя и в несколько иной области, и, как сообщается, не так ясно. « Практические диаграммы состояний UML в программировании на C / C ++ для событий встраиваемых систем » также доступны в Safari .

Horrocks цитируется довольно часто - на портале ACM есть двадцать статей , поэтому, если у вас есть доступ, вы можете найти что-то полезное.

Есть книга и программное обеспечение FlashMX для интерактивного моделирования . У них есть PDF пример главы на диаграммах состояний.

Объекты, компоненты и каркасы с UML: подход Catalysis (SM) содержит главу о поведенческих моделях, которая включает около десяти страниц полезных примеров использования диаграмм состояний (отмечу, что он доступен очень дешево подержанный). Это довольно формально и тяжело, но этот раздел легко читается.

3 голосов
/ 01 октября 2009

Я получил презентацию о шаблоне, который я называю «State First».

Это комбинация MPV / IoC / FSM, и я успешно использовал ее в .Net / WinForms, .Net / Silverlight и Flex (на данный момент).

Вы начинаете с кодирования своего FSM:

class FSM
    IViewFactory ViewFactory;
    IModelFactory ModelFactory;
    Container Container; // e.g. a StackPanel in SL
    ctor((viewFactory,modelFactory,container) {
        ...assignments...
        start();
    }

    start() {
        var view = ViewFactory.Start();
        var model = ModelFactory.Start();
        view.Context = model;
        view.Login += (s,e) => {
            var loginResult = model.TryLogin(); // vm contains username/password now
            if(loginResult.Error) {
                // show error?
            } else {
                loggedIn(loginResult.UserModel); // jump to loggedIn-state
            }
        };
        show(view);
    }


    loggedIn(UserModel model) {
        var view = ViewFactory.LoggedIn();
        view.Context = model;
        view.Logout += (s,e) => {
            start(); // jump to start
        };
        show(view);
    }

Затем вы создаете IViewFactory и IModelFactory (ваш FSM позволяет легко увидеть, что вам нужно)

public interface IViewFactory {
    IStartView Start();
    ILoggedInView LoggedIn();
}

public interface IModelFactory {
    IStartModel Start();
}

Теперь все, что вам нужно сделать, это реализовать IViewFactory, IModelFactory, IStartView, ILoggedInView и модели. Преимущество здесь состоит в том, что вы можете видеть все переходы в FSM, вы получаете über-low связь между представлениями / моделями, высокую тестируемость и (если ваш язык позволяет) безопасность большого количества типов.

Одним из важных моментов в использовании FSM является то, что вы не должны просто перепрыгивать между состояниями - вы также должны нести все данные с состоянием при прыжке (в качестве аргументов см. loggedIn выше). Это поможет вам избежать глобальных состояний, которые обычно засоряют графический код.

Вы можете посмотреть презентацию на http://prezi.com/bqcr5nhcdhqu/, но в данный момент она не содержит примеров кода.

3 голосов
/ 27 февраля 2009

Честно говоря, это не проблема пользовательского интерфейса.

Я бы сделал следующее:

  1. Определите ваши штаты
  2. Определите ваши переводы - какие состояния доступны из каких других?
  3. Как эти переходы запускаются? Какие события?
  4. Напишите свой конечный автомат - сохраните текущее состояние, получите события, и если это событие может вызвать действительный переход из текущего состояния, измените состояние соответствующим образом.
2 голосов
/ 27 февраля 2009

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

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

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

При обнаружении определенных событий состояния кнопок / экранных объектов автоматически изменяются.

Обобщенный набор состояний определенно может помочь де-споттифицировать ваш код!

2 голосов
/ 27 февраля 2009

Каждый элемент интерфейса, представленный пользователю, может перейти в другое состояние из текущего. В основном вам нужно создать карту того, какая кнопка может привести к тому, что другое состояние.

Это сопоставление позволит вам увидеть неиспользуемые состояния или состояния, в которых несколько кнопок или путей могут привести к одному и тому же состоянию и ни к каким другим (те, которые можно объединить).

1 голос
/ 27 февраля 2009

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

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

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

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

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

-Adam

...