Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アルバイト LINE BOT で使った「コマンドパターン」の紹介

kiwi
February 07, 2018

アルバイト LINE BOT で使った「コマンドパターン」の紹介

社内LT大会で登壇した際の資料です。

kiwi

February 07, 2018
Tweet

More Decks by kiwi

Other Decks in Technology

Transcript

  1. kiwi @koga_wiwi
    NIFTY Corporation
    アルバイト LINE BOT で使った
    「コマンドパターン」の紹介
    2017/10/4

    View Slide

  2. アルバイト LINE BOTについて
    対話型バイト診断 シフト時間通知 バイトみくじ

    View Slide

  3. ユーザーからのメッセージをどう処理するか
    switch message.text {
    }
    case ‘Hello’:
    message.user.friendPoint += 1;
    bot.replyMessage(‘Hello, my friend!’);
    case ‘Find Arbeit’:
    res = Arbeit.find(message.text);
    bot.replyMessage(res.title, res.link);

    View Slide

  4. ユーザーからのメッセージをどう処理するか
    switch message.text {
    }
    case ‘Hello’:
    message.user.friendPoint += 1;
    bot.replyMessage(‘Hello, my friend!’);
    case ‘Find Arbeit’:
    res = Arbeit.find(message.text);
    bot.replyMessage(res.title, res.link);
    bot.buttonMessage(‘Find More’);
    case ‘Find More’:
    res = Arbeit.find(message.text).skip(1);
    bot.replyMessage(res.title, res.link);

    View Slide

  5. 問題点
    • Controllerですべてを扱う感じになってつらい
    • でも、できるだけ運用しやすくしたい
    • コードを読みやすくする
    • 会話のパターンを増やしやすくする

    View Slide

  6. Command デザインパターン
    • ある目的を達成するための一連の処理群(前後処理、
    設定、実行)をひとつのオブジェクトにまとめて外
    部化し、マクロを呼び出す感覚で使えるようにする
    仕組み
    • 複雑な一連の処理群をオブジェクトとしてカプセル
    化し、切り出したもの
    • 登場人物
    • Invoker
    • Command
    • Receiver
    : コマンドをキューイング、実行
    : 処理対象のメソッドを手順通り実行
    : 処理対象

    View Slide

  7. LINE BOT バイト探しの場合(擬似コード)
    protocol CommandMessage {
    func init(user);
    func postQuestion(); // 質問するとき
    func receiveAnswer(answer); // 回答を受け取ったとき
    }
    class AskGender: CommandMessage {
    func init(user) { this.user = user; }
    func postQuestion() {
    bot.pushMessage(user, ‘Whats your gender?’);
    }
    func receiveAnswer(answer) {
    user.gender = answer;
    }
    }

    View Slide

  8. LINE BOT バイト探しの場合(擬似コード)
    class MessageController {
    func receive(request) {
    message = Message.from(request)
    commandForReceived = null;
    commandForPost = null;
    switch message.type {
    case MessageTypeStart:
    commandForPost = AskGender(message.user);
    case MessageTypeGender:
    commandForReceived = AskGender(message.user);
    commandForPost = AskStudent(message.user);
    }
    commandForReceived?.receiveAnswer(message);
    commandForPost?.postQuestion();

    View Slide

  9. アルバイトアプリ版の構成と設計
    MessageController
    どのCommandを
    実行するか管理する
    Commandを実行する
    HogeCommand
    メッセージの送信
    メッセージ受信時の処理
    FugaCommand
    メッセージの送信
    メッセージ受信時の処理
    User
    Invoker Command
    Receiver

    View Slide

  10. 悩みどころ
    class MessageController {
    func receive(request) {
    message = Message.from(request)
    commandForReceived = null;
    commandForPost = null;
    switch message.type {
    case MessageTypeStart:
    commandForPost = AskGender(message.user);
    case MessageTypeGender:
    commandForReceived = AskGender(message.user);
    commandForPost = AskStudent(message.user);
    }
    commandForReceived?.receiveAnswer(message);
    commandForPost?.postQuestion();

    View Slide

  11. 受け取ったメッセージをどのコマンドに処理させるか
    • メッセージテキストを正規表現で切り出したり
    • 特定のメッセージパターンだったり
    • ユーザーの状態から判断したり

    View Slide

  12. 受け取ったメッセージをどのコマンドに処理させるか
    • メッセージテキストを正規表現で切り出したり
    • 特定のメッセージパターンだったり
    • ユーザーの状態から判断したり
    するけど、その判別はどこで……?

    View Slide

  13. 悩みどころ
    class MessageController {
    func receive(request) {
    message = Message.from(request)
    commandForReceived = null;
    commandForPost = null;
    switch message.type {
    case MessageTypeStart:
    commandForPost = AskGender(message.user);
    case MessageTypeGender:
    commandForReceived = AskGender(message.user);
    commandForPost = AskStudent(message.user);
    }
    commandForReceived?.receiveAnswer(message);
    commandForPost?.postQuestion();
    別のところにある……

    View Slide

  14. たとえば
    • メッセージ判別メソッド
    • メッセージの受信~次のメッセージの送信
    をまとめてみる
    class ReceiveGender: CommandMessage {
    class func init(user) { this.user = user; }
    func receiveAnswer(answer) {
    user.gender = answer;
    bot.pushMessage(user, ‘You are student?’);
    }
    }

    View Slide

  15. たとえば
    class MessageController {
    let mesToCmd: [((Message -> bool), class)] = [
    ];
    func receive(message) {
    user = message.user;
    command = null;
    for tuple in mesToCmd {
    if tuple[0](message) {
    command = tuple[1];
    break;
    }
    }
    command?.receiveAnswer();
    ({mes in return mes.text == ‘Start’}, AskGender.class),
    ({mes in return mes.data == ‘Gender’}, ReceiveGender.cl

    View Slide

  16. この場合
    • 配列とクラスを追加すれば応答パターンを増やせる
    • 返答に応じて次の質問が変わる場合の処理が
    Controllerからいなくなる。処理が多いときは良い
    • フロー的にはわかりづらくなった気がする
    • 質問と回答が同じクラスにない
    AskGender
    性別を聞くときの処理
    性別が送られたときの処理
    Start
    性別を聞くときの処理
    ReceiveGender
    性別が送られたときの処理
    学生かどうか聞くときの処理

    View Slide

  17. まとめ
    • フルスクラッチで作ろうとすると、設計が難しい
    • それでもこのパターンを使えば少しはましになると
    思うので、よければ使ってみてください
    • フレームワークとかもあるので、
    そういうのを使うのも良いと思います

    View Slide