Behaviours in Javascript?

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>
3 Likes