今天要來講的「責任鏈(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
來做升級)
昨天我們已經看了「Router」的應用,大致上知道我們可以利用 Router 做分流,但如果對話或操作相對複雜,要用一個 Router 來分流所有事情對模組化可能不是一件好事。
我們也知道可以使用多層的 Router,但那 Router 未必是一個好的方式來呈現模組之間的優先順序,因此我們還是需要一個「責任鏈」來表示,例如說,如果我們的機器人其實有多個理解層:
那其實是等一個 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 來處理,優先順序是從上到下:
如果你回傳了 next
,就會繼續地往下走,但如果回傳了 next
以外的東西,那就會斷在那邊,不會再往下。
實際跑起來,並試看看打開 Debug 訊息,也看得出來 Middleware 的階層:
這次在設計的時候,非常強調「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;
};
它們各自解決不同的問題,很適合搭配使用。
除了優先順序以外,有時候也能用功能去切分,只要讓彼此相關的部分集中在同一個模組裡,都是好的設計。階層該怎麼設計沒有正確答案,這就要看設計功能的人自己怎麼想,試圖讓可讀性跟可維護性是高的就好。