Behaviours use a conceptual contract between the (generic) behaviour module and the (specialized) callback module - but that doesn’t mean a behaviour is-like an interface - because that doesn’t look at the whole picture.
Is the strategy pattern like an interface? No, the Strategy implements the interface that the Context accepts.
For some more details see here.
So in JavaScript you would use the Strategy Pattern
Context -> Behaviour Module
Strategy -> Callback Module
The Context is the generic part, while the Strategy is the specialized part. Inject (i.e. compose) the Strategy into the Context instance upon creation so that the Strategy can affect the Context’s behaviour at runtime.
(function() {
// Context/Behaviour module
class Server {
start(callbackModule, initArg) {
let [_, state] = callbackModule.init(initArg)
this.state = state
this.cbModule = callbackModule
}
call(request) {
let [_, reply, new_state] = this.cbModule.handle_call(request, this.state)
this.state = new_state
return reply
}
cast(request) {
let [_, new_state] = this.cbModule.handle_cast(request, this.state)
this.state = new_state
}
}
// Strategy/Callback module
class Stack {
static init(stack) {
return [true, stack]
}
static handle_call(request, state) {
let status = false
let reply = null
let new_state = state
switch(request) {
case 'POP':
reply = state.shift()
// state mutated new_state
status = true
break;
}
return [status, reply, new_state]
}
static handle_cast(request, state) {
let status = false
let new_state = state
if (!Array.isArray(request)) {
return [status, new_state]
}
switch(request[0]) {
case 'PUSH':
state.unshift(request[1])
// state mutated new_state
status = true
break;
}
return [status, new_state]
}
}
let server = new Server()
server.start(Stack, ['hello'])
let result = server.call('POP')
console.log(result)
server.cast(['PUSH','world'])
result = server.call('POP')
console.log(result)
}())
Module-based version:
// file: server.mjs
// Context/Behaviour module
export async function start(callbackName, initArg) {
let cbModule = await import(callbackName);
let [_, state] = await cbModule.init(initArg);
return {cbModule, state};
}
export async function call(server, request) {
let {cbModule, state} = server;
let [_, reply, new_state] = await cbModule.handleCall(request, state);
return [{...server, state: new_state}, reply];
}
export async function cast(server, request) {
let {cbModule, state} = server;
let [_, new_state] = await cbModule.handleCast(request, state);
return {...server, state: new_state};
}
// file: stack.mjs
// Strategy/Callback module
export async function init(stack) {
return [true, stack];
}
export async function handleCall(request, state) {
let status = false;
let reply = null;
let new_state = state;
switch(request) {
case 'POP':
reply = state.shift();
// state mutated new_state
status = true;
break;
}
return [status, reply, new_state];
}
export async function handleCast(request, state) {
let status = false;
let new_state = state;
if (!Array.isArray(request)) {
return [status, new_state];
}
switch(request[0]) {
case 'PUSH':
state.unshift(request[1]);
// state mutated new_state
status = true;
break;
}
return [status, new_state];
}
<!DOCTYPE html>
<html lang="en">
<!-- file: index.html -->
<head>
<meta charset="utf-8">
<title>Behaviours in JavaScript</title>
</head>
<body>
<h1>Behaviours in JavaScript</h1>
<p>Check the developer tools console</p>
<p style="font-size:0.7rem">Works in Chrome 78.0.3904.108 with http-server@0.11.1</p>
</body>
<script type="module">
import {start, call, cast} from './server.mjs';
async function run() {
let server = await start('./stack.mjs', ['hello']);
let result;
[server, result] = await call(server, 'POP');
console.log(result);
server = await cast(server, ['PUSH','world']);
[server, result] = await call(server, 'POP');
console.log(result)
}
run();
</script>
</html>