編集系ソフトウェアとデータモデル
id:Yamashiro0217 ちょうどドローツールっぽいツールを作ろうとしてるところで、「くGUIの編集系のソフトウェアについていろいろ書きたいのでまだまだ続きますよ!」にすごい期待です!
ymsr先生からこんなコメントをいただいたので調子に乗って書いてみる。
編集系のソフトウェアはデータモデルが超重要です。どうしてもGUIの見た目や操作性の部分に気持ちが行ってしまいそうですが、データモデルをしっかり固めてしまえば、GUIの見た目や操作というのはいくらでも差し替えが利きます。
使い勝手の部分というのはころころ変わりますが、データモデルがしっかりしていれば、見た目や操作の部分に変更があってもデータモデル自体に手を加える必要はありません。逆にデータモデルの変更はGUIに大きく影響をあたえます。
前回までUndo、Redoの説明をしましたが、ここで出てきたのはデータモデルだけです。これにどんなGUIが付こうともここの部分は変わりません。コピー&ペーストも同じです。データモデルに対してコピー、ペーストという操作を行います。
MVCの重要さはここにあります。ViewがちゃんとModelの変更に追従していさえすれば、Undo、Redoやコピー&ペーストといった一見複雑な処理もModelのなかだけで完結することが出来るのです。
そのためにはModelの受け口はひとつであることも重要です。Modelの変更をいろいろなところから受け付けられるとModelの一貫性を保つことが大変です。また、非同期に変更が起こればModelの一貫性が破綻することは目に見えています。
そこでコマンドが重要な役割を果たします。データモデルの更新はコマンドを発行するというひとつの入り口に絞り込むことが出来ます。そしてコマンドの実行をシリアルに行えば非同期の問題も解決できるのです。
同期化の問題には2つの解法があります。
ひとつはコマンドを実行するメソッドを排他制御することです。この方法はJavaのようにsynchronizedキーワードなどによって簡単に同期化することが出来る言語ではとても手軽な方法です。ただし、排他制御による同期化はロックが生じてしまいます。また、デッドロックの危険も潜んでいます。さきほど説明したようにViewはデータモデルに追従する必要があります。しかしこの処理が結構なコストがかかる場合があります。更新処理の待ち時間が馬鹿にならないこともあるでしょう。GUIスレッドのロックは使い勝手に大きな影響を与えます。
そこでわたしがよく使う方法がブロッキングキューによるコマンドの非同期実行です。
コマンドを発行する側はコマンドを実行するのではなく、ブロッキングキューにコマンドをプッシュします。コマンドの実行自体は別のスレッドで行います。l
そのため、たとえばGUIスレッドからコマンドを発行したとしても、GUIがコマンドの実行が終了するのを待たずに次の処理を行うことが出来ます。
コマンドを実行する部分のコードは以下のようになります。
public class CommandInvoker implements Runnable { private BlockingQueue<ICommand> mQueue; public CommandInvoker() { mQueue = new LinkedBlockingQueue<ICommand>(); new Thread(this).start(); } public void run() { try { while (true) { ICommand command = mQueue.take(); command.invoke(); } } catch (InterruptedException e) {} } public void invokeCommand(ICommand command) { try { mQueue.put(command); } catch (InterruptedException e) {} } }