フロントエンドのウィジェット コードをスパゲッティ化する方法を探しています。私がやっていることを考えるには、有限状態マシンが正しい方法であることが示唆されています。私は、ステート マシンのパラダイムがほとんどすべての問題に適用できることを知っています。実際にこれを習慣化している経験豊富なUIプログラマーがいるのだろうか.
質問は、あなたの UI プログラマーの中で、あなたの仕事でステート マシンの観点から考えている人はいますか? もしそうなら、どのように?
ありがとう -モーガン
フロントエンドのウィジェット コードをスパゲッティ化する方法を探しています。私がやっていることを考えるには、有限状態マシンが正しい方法であることが示唆されています。私は、ステート マシンのパラダイムがほとんどすべての問題に適用できることを知っています。実際にこれを習慣化している経験豊富なUIプログラマーがいるのだろうか.
質問は、あなたの UI プログラマーの中で、あなたの仕事でステート マシンの観点から考えている人はいますか? もしそうなら、どのように?
ありがとう -モーガン
私は現在、ステート マシンとしての UI パラダイムに適した (独自の) フレームワークを使用しており、UI 要素間の複雑で予期しない相互作用の問題を確実に減らすことができます (ただし、なくすことはできません)。
主な利点は、より高いレベルの抽象化、より高い粒度で考えることができることです。「ボタン A を押すとコンボボックス B がロックされ、テキストフィールド C がクリアされ、ボタン D がロック解除される」と考える代わりに、「ボタン A を押すとアプリが CHECKED 状態になる」と考えます。起こる。
ただし、UI 全体を 1 つのステート マシンとしてモデル化することは有用ではないと思います (または可能でさえありません)。代わりに、通常、それぞれが UI の一部 (概念的に相互に作用して関連する複数のコントロールで構成される) を処理するいくつかの小さなステート マシンと、より基本的な問題を処理する 1 つの (場合によっては複数の) "グローバル" ステート マシンがあります。
一般に、ステート マシンは低レベルすぎて、ユーザー インターフェイスについて考えるのに役立ちません。これらは UI ツールキットの実装としては適切な選択ですが、通常のアプリケーションで説明するには状態と遷移が多すぎて、手動で説明することはできません。
継続のある UI について考えるのが好きです。(Google で検索してください。この用語は十分に具体的であるため、多くの高品質のヒットが得られます。)
アプリがステータス フラグやモードによって表されるさまざまな状態になる代わりに、継続を使用して、アプリが次に何をするかを制御します。例を挙げて説明するのが最も簡単です。メールを送信する前に確認ダイアログを表示したいとします。ステップ 1 では、電子メールを作成します。ステップ 2 は確認を取得します。ステップ 3 では、電子メールを送信します。ほとんどの UI ツールキットでは、各ステップの後に制御をイベント ループに戻す必要があるため、ステート マシンで表現しようとすると、これは非常に見苦しくなります。継続では、ツールキットが強制するステップの観点から考えるのではなく、メールを作成して送信するすべての 1 つのプロセスです。ただし、プロセスで確認が必要な場合は、継続でアプリの状態をキャプチャし、その継続を確認ダイアログの [OK] ボタンに渡します。OKを押すと、
プログラミング言語では継続は比較的まれですが、幸運なことに、クロージャーを使用して一種の貧弱なバージョンを得ることができます。メール送信の例に戻ると、確認が必要な時点で残りのプロセスをクロージャーとして記述し、そのクロージャーを [OK] ボタンに渡します。クロージャーは、次に呼び出されるときにすべてのローカル変数の値を記憶する、匿名のネストされたサブルーチンのようなものです。
うまくいけば、これがあなたに考えるためのいくつかの新しい方向性を与えるでしょう. 後で実際のコードを示して、それがどのように機能するかを示します。
更新: Ruby で Qt を使用した完全な例を次に示します。興味深い部分は、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()
ステート マシンとしてモデル化する必要があるのは UI ではありません。ステート マシンとしてモデル化するのに役立つのは、表示されているオブジェクトです。そうすると、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(); }
クライアント UI 側とサーバー Foo 側の両方から、表示しているデータにどのような変更が加えられたかを考えると、再描画によって明確になるはずです。
Foo の状態が変化したときに再描画する必要があるかもしれない 100 個の UI のうち、パレットが変化したときにそれらすべてを再描画する必要がありますが、幅が変化したときに 10 個だけを再描画する必要があることがわかった場合、どのイベント/状態について何かを示唆している可能性があります変更 Foo はシグナリングする必要があります。UI の更新を最小限に抑えるために、多くの Foo のプロパティをチェックして何が変更されたかを確認する大規模なイベント ハンドラー OnFooStateChanged() がある場合は、Foo のイベント モデルの粒度に関する何かが示唆されます。UI の複数の場所で使用できる小さなスタンドアロン UI ウィジェットを作成したいが、いつ Foo が変更されたかを知る必要があり、Foo の実装がもたらすすべてのコードを含めたくない場合は、 UIに関連するデータの編成について何かを提案します。「フォームクラスのすべてのコード」よりも真剣に、プレゼンテーションレイヤーです。
-パソコン
この話題についての本があります。残念なことに絶版で、入手可能な希少な中古品は非常に高価です。
Constructing the User Interface with Statecharts
by Ian Horrocks, Addison-Wesley, 1998
Horrocks のConstructing the User Interface with Statechartsについて話していたところです。中古品の価格は 250 ドルから 700 ドル近くまでの範囲です。私たちのソフトウェア開発マネージャーは、これを彼が持っている最も重要な本の 1 つと評価しています (残念ながら、彼は地球の反対側に住んでいます)。
州図に関する Samek の本は、この研究からかなりの部分を引き出していますが、ドメインはわずかに異なり、明確ではないと伝えられています。「組込みシステム向け C/C++ イベント駆動型プログラミングの実用的な UML ステートチャート」もSafariで入手できます。
Horrocks は非常に頻繁に引用されています。ACM ポータルには20 の論文があるため、そこにアクセスできる場合は、役立つものが見つかるかもしれません。
インタラクティブ シミュレーション用の本とソフトウェアFlashMXがあります。ステートチャートに関するPDFサンプルの章があります。
UML を使用したオブジェクト、コンポーネント、およびフレームワーク: The Catalysis(SM) Approachには、動作モデルに関する章があり、ステートチャートを使用する有用な例が約 10 ページ含まれています (非常に安価に中古で入手できることに注意してください)。かなり形式的で重い話ですが、そのセクションは読みやすいです。
正直なところ、UI の問題ではありません。
私は次のことをします:
私が "State First" と呼んでいるパターンについて prezi プレゼンテーションを受け取りました。
これは 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 ですべての遷移を確認できることです。ビュー/モデル間のカップリングが非常に低く、テスト容易性が高く、(言語が許せば) 大量の安全な型が得られます。
FSM を使用する際の重要なポイントの 1 つは、状態間をジャンプするだけでなく、すべてのステートフル データをジャンプで運ぶ必要があることです (引数として、loggedIn
上記を参照)。これは、通常 GUI コードを散らかすグローバルな状態を回避するのに役立ちます。
プレゼンテーションはhttp://prezi.com/bqcr5nhcdhqu/で見ることができますが、現時点ではコード例は含まれていません。
ユーザーに提示される各インターフェース項目は、現在の状態から別の状態に移行できます。基本的に、どのボタンが他のどの状態につながるかについてのマップを作成する必要があります。
このマッピングにより、未使用の状態、または複数のボタンまたはパスが同じ状態になり、他の状態にはならない状態(組み合わせることができる状態)を確認できます。
モーガンさん、ここ Radical では AS3 でカスタム フレームワークを構築しており、ステート マシン パラダイムを使用してフロント エンドの UI アクティビティを強化しています。
すべてのボタン イベント、すべての表示イベントなどに対するステート マシンのセットアップがあります。
AS3 はイベント駆動型言語であるため、これは非常に魅力的なオプションです。
特定のイベントがキャッチされると、ボタン/表示オブジェクトの状態が自動的に変更されます。
一般化された一連の状態を持つことは、間違いなくコードのスパゲッティ化に役立ちます!
ステートマシンは、コードが他のステートマシンと連携できるようにするものです。ステートマシンは、過去のイベントのメモリを持つ単なるロジックです。
したがって、人間はステートマシンであり、多くの場合、ソフトウェアが過去に行ったことを記憶して続行できることを期待しています。
たとえば、調査全体を1ページにまとめることができますが、人々は複数の小さなページの質問に慣れています。ユーザー登録も同様です。
したがって、ステートマシンはユーザーインターフェイスに多くの適用性があります。
ただし、デプロイする前にそれらを理解する必要があります。また、コードを記述する前に設計全体を完了する必要があります。ステートマシンは悪用される可能性があり、悪用され、悪用される可能性があります。使用する理由が明確にわからない場合は、 1つ、そして目標が何であるか、あなたは他のテクニックよりも悪い結果になるかもしれません。
-アダム