iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
1
Modern Web

使用 Modern Web 技術來打造 Chat App系列 第 20

Day 20:責任鏈模式 - 「Chain」的思考

  • 分享至 

  • xImage
  •  

今天要來講的「責任鏈(Chain of Responsibility)」模式,這是一個很常見的軟體設計模式,尤其是在 Node.js 上可能很多人都非常熟悉,例如:最常見的 Express 伺服器框架,我們必須利用 app.use 來去使用 Middleware,這就是一個責任鏈模式:

app.use(middleware1);
app.use(middleware2);

每個 Middleware 都可以去判斷自己是否能處理對應的事件,而如果前面的 Middleware 把事件處理掉了,那後面的 Middleware 就不會收到,也就是說他有個明確的優先順序。

對責任鏈模式了解到這邊大概就夠了,我們來看看怎麼在 Bottender 上使用「責任鏈」吧!

(注意:這篇文章需要使用最新的版本,可以用 npm update bottender 或是 yarn upgrade bottender 來做升級)

應用在 Bottender 上

昨天我們已經看了「Router」的應用,大致上知道我們可以利用 Router 做分流,但如果對話或操作相對複雜,要用一個 Router 來分流所有事情對模組化可能不是一件好事。

我們也知道可以使用多層的 Router,但那 Router 未必是一個好的方式來呈現模組之間的優先順序,因此我們還是需要一個「責任鏈」來表示,例如說,如果我們的機器人其實有多個理解層:

  1. 規則層(關鍵字、正規表達式)
  2. 機器學習層(Deep Learning Model)
  3. 聽不懂的回應或真人客服

那其實是等一個 Layer 無法處理的時候就要再往後丟,就跟一個客服處理不來可能還要拉其他人下水一樣。

在 Bottender 使用,首先我們要使用 chain 這個 Function:

const { chain } = require('bottender');

async function SayHi(context) {
  await context.sendText('hi');
}

const Chain = chain([
  function RuleBased(context, { next }) {
    if (context.event.text === 'hi') {
      // discontinue
      return SayHi;
    }
    return next;
  },
  function MLBased(context, { next }) { /* ...省略 */ },
  function HumanAgent(context, { next }) { /* ...省略 */ },
]);

module.exports = function App() {
  return Chain;
};

如同上面這段程式所描述,我們用三個不同的 Action 來處理,優先順序是從上到下:

  • RuleBased
  • MLBased
  • HumanAgent

如果你回傳了 next,就會繼續地往下走,但如果回傳了 next 以外的東西,那就會斷在那邊,不會再往下。

實際跑起來,並試看看打開 Debug 訊息,也看得出來 Middleware 的階層:

https://ithelp.ithome.com.tw/upload/images/20191005/20103630HEdvhQyrQY.png

與 Router 跟 Compose 的搭配

這次在設計的時候,非常強調「Chain」、「Router」、「Compose」這些互相搭配的機制必須能很好的相容,所以他們都是回傳一樣格式的 Action,所以完全可以像下面這樣去寫:

const { chain } = require('bottender');
const { router, text } = require('bottender/router');
const { random, sendText } = require('bottender-compose');

const SayHi = random([
  sendText('hi'),
  sendText('hello'),
]);

const Chain = chain([
  function RuleBased(context, { next }) {
    return router([
      text('hi', SayHi),
      text('*', next),
    ]);
  },
  function MLBased(context, { next }) { /* ...省略 */ },
  function HumanAgent(context, { next }) { /* ...省略 */ },
]);

module.exports = function App() {
  return Chain;
};

它們各自解決不同的問題,很適合搭配使用。

結語

除了優先順序以外,有時候也能用功能去切分,只要讓彼此相關的部分集中在同一個模組裡,都是好的設計。階層該怎麼設計沒有正確答案,這就要看設計功能的人自己怎麼想,試圖讓可讀性跟可維護性是高的就好。


上一篇
Day 19:使用「Router」來做功能分流
下一篇
Day 21:什麼是「有限狀態機」?
系列文
使用 Modern Web 技術來打造 Chat App30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言