目標
前回のカウンターアプリケーションの主にコードの中身を改良します。
カウント数の表示形式を変更し、起動時のカウント数の初期値を設定します。
カウンターアプリ
依存の逆転
さて、前回はシンプルなカウンターアプリケーションを作成しましたが、カウント数の表示形式を変えるのが難しいという問題がありました。
そこで、コードを保守しやすい形に書き直していきましょう。
論点はシンプルです。カウント数を表示用テキストに変換するのは簡単ですが、その逆は容易ではありません。
それにもかかわらず、現在はlabelCount.Text
を基にしてカウント数を取得しています。
最初からカウント数を数値として保持しておけば話が簡単になるはずです。早速やってみましょう。
カウント数を保持するための変数count
を変更可能な変数として宣言します。
let mutable count = 0
クリックされたときに、変数count
の値を上書きし、上書き後の値をラベルに表示します。
let subscription =
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
count <- count + 1
this.labelCount.Text <- $"Count: %d{count}")
最終的に次のようなコードになります。
module MainForm
open App.Designer
type View() as this =
inherit MainForm()
let mutable count = 0
let subscription =
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
count <- count + 1
this.labelCount.Text <- $"Count: %d{count}")
let subscription2 =
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
count <- count - 1
this.labelCount.Text <- $"Count: %d{count}")
override this.Dispose(disposing) =
if disposing then
subscription.Dispose()
subscription2.Dispose()
base.Dispose disposing
コードの中身は変わりましたが、挙動は前回と同じになるはずです。
ここで重要なのは、今までカウント数を保持していたのはラベルのテキスト(文字列)でしたが、今回のコードではcount
変数(数値)が保持しているという違いです。
このように、アプリケーションの状態の本質を表すようなデータをモデルと呼ぶことにします。
共通ロジックの集約
さて、先ほどのコードを見ていると、全く同じ記述をしている箇所があることに気がつきます。
this.labelCount.Text <- $"Count: %d{count}"
カウント数をラベルに表示する部分のコードは全く同じになります。
しかし、よくよく考えてみるとそれ依然に、count
の値が変更されたときは毎回ラベルを更新する必要があることに気が付くはずです。
count
の値が変更されたらそれに合わせてラベルのテキストも更新する必要があるため、これはひとまとまりの処理と言えます。
そこで、setCount
という関数を作成し、共通化することにしましょう。
let setCount nextCount =
count <- nextCount
this.labelCount.Text <- $"Count: %d{count}"
変数count
の値を上書きする際は、直接<-
を使うのではなく代わりにsetCount
関数を使うようにします。
これにより、ラベルのテキストも合わせて変更されるようになります。
setCount
関数を使うように既存のコードを書き換えてみましょう。
module MainForm
open App.Designer
type View() as this =
inherit MainForm()
let mutable count = 0
+ let setCount nextCount =
+ count <- nextCount
+ this.labelCount.Text <- $"Count: %d{count}"
let subscription =
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
- count <- count + 1
- this.labelCount.Text <- $"Count: %d{count}")
+ setCount (count + 1))
let subscription2 =
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
- count <- count - 1
- this.labelCount.Text <- $"Count: %d{count}")
+ setCount (count - 1))
override this.Dispose(disposing) =
if disposing then
subscription.Dispose()
subscription2.Dispose()
base.Dispose disposing
初期値の変更
さて、ここまではカウント数の初期値を0
としていましたが、この初期値を例えば42
に変更してみましょう。
- let mutable count = 0
+ let mutable count = 42
ここでアプリケーションを実行してみると、初期値が反映されていません。
初期値の変更が反映されない
これは、フォームデザイナで設定したテキストがそのまま表示されているからです。
初期値を変えるたびに合わせてフォームデザイナの設定を変えるのは現実的ではないため、コード上で初期値をラベルに反映させることにします。
module MainForm
open App.Designer
type View() as this =
inherit MainForm()
let mutable count = 42
let setCount nextCount =
count <- nextCount
this.labelCount.Text <- $"Count: %d{count}"
+ do setCount count
let subscription =
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
setCount (count + 1))
let subscription2 =
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
setCount (count - 1))
override this.Dispose(disposing) =
if disposing then
subscription.Dispose()
subscription2.Dispose()
base.Dispose disposing
これにより、コード上で指定した初期値が反映されるようになります。
初期値の反映
表示形式の変更
さて、これでカウント数を数値で保持することができるようになりました。
これにより、カウント数の表示方法を比較的簡単に変更できます。
早速、Count: XXX
という表示から現在のカウント数は XXX です。
という表示に変更してみましょう。
- this.labelCount.Text <- $"Count: %d{count}"
+ this.labelCount.Text <- $"現在のカウント数は %d{count} です。"
カウンターアプリの実行
初期値が反映されるようにコードで記述しているので、フォームデザイナのほうは変更する必要がありません。
フォームデザイナ
購読解除の定型化
最後に、イベントの購読解除を少し楽にしておきましょう。
まだイベントは2つなので管理できていますが、今後イベントが増えてくると購読のたびにそれを命名しなければなりません。
現在のようにsubscription
, subscription2
など機械的に命名していくと、管理が煩雑になり解除漏れも発生しやすくなります。
そこで、逐一命名をせずに済む方法を考えましょう。
各購読について個別に変数を用意するのではなく、配列を用意してそこでまとめて購読を管理することにします。
F#の配列は[| ... |]
で要素を囲みます。区切り文字はセミコロン;
ですが、改行した場合はこれを省略することができます。
let subscriptions = [|
this.buttonincrement.click
|> Observable.subscribe (fun e ->
setCount (count + 1))
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
setCount (count - 1))
|]
フォームを使い終わったタイミングで配列subscriptions
の各要素をまとめてDispose
します。
今回はArray.iterを使います。
subscriptions
|> Array.iter (fun subscription -> subscription.Dispose())
最終的に次のようなコードになります。
module MainForm
open App.Designer
type View() as this =
inherit MainForm()
let mutable count = 42
let setCount nextCount =
count <- nextCount
this.labelCount.Text <- $"現在のカウント数は %d{count} です。"
do setCount count
let subscriptions = [|
this.buttonincrement.click
|> Observable.subscribe (fun e ->
setCount (count + 1))
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
setCount (count - 1))
|]
override this.Dispose(disposing) =
if disposing then
subscriptions
|> Array.iter (fun subscription -> subscription.Dispose())
base.Dispose disposing
これにより、各購読を命名する必要が無くなりました。
個別にイベントの購読解除タイミングを管理する必要がない場合はこの方法で良いでしょう。