前回から引き続き、本題の Scorables の実装です。
Overview
- 0. Scorables とは (前回)
- 1. デモ用ボットの作成(前回)
- 2. 簡易なScorableの実装(今回はココから!)
- 3. DIの実装
- 4. Scorableの実装からDialogへ
- 5. Scorableの実装の注意点
Environment
- Visual Studio 2017
- .NET Framework 4.6
- (NuGet) Microsoft.Bot.Builder v3.8.5
Scorables の実装のイメージとしては、
- スコアリングをするクラスの作成
- (必要であれば)トップスコアだった場合に処理をするDialogの作成
です。
2. 簡易なScorableの実装
会話をリセットする Scorables の実装
よくある機能?、会話をリセットする Scorables を実装します。動作イメージは、「リセット」と入力されたら、会話がリセットされるシンプルな例です。
Dialog フォルダーの下に Global というフォルダーを作り、ResetScorable
というクラスを作りました(フォルダー構成イマイチですね)。
まずはコードを。
"はじめてのScorables"だと説明どころはたくさんあるんですが...ざっくり行きましょう。
Socrableなクラスを実装するには、ScorableBase
クラスを継承させます。13行目のGenericの型パラメーターについては、後述します。詳しくはBotBuilderのソースのここら辺とかですが、更新頻度も多いと思いますので、確認したときに都度ここから掘っていくと良いかも。
ざっくり解説:コンストラクター
コンストラクターの引数で IDialogTask
を受け取っています(17行目)が、これはDIしてるの、実装については後述します。
処理としては、SetField.NotNull
メソッドを呼んでいるだけです(19行目)。これは BotBuilder 内のメソッドで、3つ目の引数(コンストラクターで受け取った値)がNULLだったら2つめの引数(ここだと nameof(_task)
)のExceptionをはく、NULLでなければ1つめの引数に値をセットするというよくある処理です。Guardメソッドですね。
ざっくり解説:Scorables 内の流れ
ResetScorable
クラスで実装しているメソッドは、ScorableBase
クラスの abstract なメソッドのみです。コンストラクターが実行されて以降の基本的な流れをざっくり表にしました。
処理順 | メソッド名 | 概要 |
---|---|---|
1 | PrepareAsync | スコアを判別するための前処理をします。戻り値の型は、ScorableBaseのGenericの2つめの型パラメーターとなります。 |
2 | HasScore | PrepareAsyncの戻り値が、このメソッドの2つ目の引数に入ってきます。このメソッドの戻り値は、スコアを持っているか否かです。 |
3 | GetScore | HasScoreメソッドの処理が true の場合のみ、スコアを出力します。スコアの型は、ScorableBaseのGenericの3つめの型パラメーターとなります。 |
4 | PostAsync | 複数の Scorables がある場合、トップスコアのクラスのPostAsyncメソッドが実行されます。 |
5 | DoneAsync | PostAsync後の後処理を実装します。 |
あくまでざっくりです。正確に把握したい場合は、ソースコードを読むのが確実です。コードだけだと読むの辛いので、BotBuilder自体をデバッグすると追いやすかったです。
今回の実装である ResetScorable クラスで具体的に説明すると、
処理順 | メソッド名 | 概要 |
---|---|---|
1 | PrepareAsync | 22行目。ユーザーが入力したメッセージが「リセット」という文字列であれば、その文字列を返す |
2 | HasScore | 33行目。2つめの引数(=PrepareAsyncメソッドの戻り値)がNULLでなければtrue(スコアがある)、NULLだったらFalse(スコアがない)を出力 |
3 | GetScore | 38行目。スコア1.0を返す。 |
4 | PostAsync | 43行目。処理として会話をリセットする。 |
5 | DoneAsync | 45行目。後処理は特になし。 |
具体的なアクションをするのであれば、Dialog を実装して PostAsync
メソッドで呼び出すようにするとよいです(後述)。
とりあえずここまでを動かしてたいので、Autofac の実装をしてしまいましょう。
3. DIの実装
Application_Start()
メソッド(Global.asax.cs
)で実装しました。
Autofac 自体の説明をすると本質的なところから外れるので省略しますが、21行目あたりの書き方で実装していく感じです。IDialogTask
自体のDIは BotBuilder 側でやっているのでここでは呼ぶだけです。ガチで開発するとコードも増えるので、責務に応じてModule化してDIの登録をするのが現実的ですね。
Instance Scope も大事ですが...リンクのみにします。
Controlling Scope and Lifetime — Autofac 4.0 documentation
では、さっくりデバッグしてみましょう。
「リセット」と入力すると、Dialogがリセットされ、会話が最初からになります(動作がちょっとバカっぽいですが...サンプルなので...)。
4. Scorableの実装からDialogへ
ユーザーが「ヘルプ」と入力したら、HelpDialog
を呼び出す Scorables を実装してみます。
先に HelpDialog
を作ります。Globalフォルダの下に HelpDialog
クラスを作成してコードを実装します。
Dialogが呼ばれたらとりあえず挨拶して、ユーザーからの入力を確認したら処理終了という、ヘルプ機能とは完全に無縁な実装です。なんてことでしょう。
そして本題の Scorables。
先ほど実装したのと大差ありません。28行目で、「ヘルプ」と入力されたかを判断しています。
ポイントは、PostAsync
メソッド(46行目)です。DialogStackに割り込んでダイアログの呼び出すには、この方法でできます。message
がnullでなければって処理を書いていますが、特に意味はなく、色々試していたゴミが残っただけです。
後は、HelpScorable の DIを実装です。先ほど書いた(Global.asax.cs
の)ResisterDependency
メソッドで今回の部分を追加します(9~11行目)。
( HelpScorable
クラス内にある、52行目の TODO// DI
は今回は放置...)
説明が雑ですが、実行してみましょう。
5. Scorableの実装の注意点
実装して気づいた点を2つ上げておきます。
DialogStackに差し込まれることは理解しておく
今回のサンプルだと、「ヘルプ」を連発すれば、その分HelpDialog
がネストされてグダグダになります。ガチで実装する際は、状態の管理が必要かなーと思います。スコアリングに注意
今回はスコアをすべて1.0を返していますが、これだと複数の Scorables を実装した時に同一の点数になることもあります。
同一のスコアが複数存在する場合を試したところ、DIで先に登録した方が動作しました(今後もそうなるかはわかりませんが)。意味不明な動作をしないようスコアの管理はちゃんとしましょう♪
ちなみに同一スコアの事故は、以前に「ユーザーとボット」の会話から「ユーザーとオペレーター(人)」の会話に切り替える実装をしたときにバグらせた経験が...Scorables を作れば作るほどスコア管理大事です♪