Re-export to extend

Re-export to extend

Often you will need to extend functionality on a module. A common JS pattern is to augment the original object with extensions, similar to how JQuery extensions work. As we’ve mentioned before, modules do not merge like global namespace objects would. The recommended solution is to not mutate the original object, but rather export a new entity that provides the new functionality.

Consider a simple calculator implementation defined in module Calculator.ts. The module also exports a helper function to test the calculator functionality by passing a list of input strings and writing the result at the end.

Calculator.ts

export class Calculator {
  private current = 0;
  private memory = 0;
  private operator: string;

  protected processDigit(digit: string, currentValue: number) {
    if (digit >= "0" && digit <= "9") {
      return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0));
    }
  }

  protected processOperator(operator: string) {
    if (["+", "-", "*", "/"].indexOf(operator) >= 0) {
      return operator;
    }
  }

  protected evaluateOperator(operator: string, left: number, right: number): number {
    switch (this.operator) {
      case "+": return left + right;
      case "-": return left - right;
      case "*": return left * right;
      case "/": return left / right;
    }
  }

  private evaluate() {
    if (this.operator) {
      this.memory = this.evaluateOperator(this.operator, this.memory, this.current);
    }
    else {
      this.memory = this.current;
    }
    this.current = 0;
  }

  public handelChar(char: string) {
    if (char === "=") {
      this.evaluate();
      return;
    }
    else {
      let value = this.processDigit(char, this.current);
      if (value !== undefined) {
        this.current = value;
        return;
      }
      else {
        let value = this.processOperator(char);
        if (value !== undefined) {
          this.evaluate();
          this.operator = value;
          return;
        }
      }
    }
    throw new Error(`Unsupported input: '${char}'`);
  }

  public getResult() {
    return this.memory;
  }
}

export function test(c: Calculator, input: string) {
  for (let i = 0; i < input.length; i++) {
    c.handelChar(input[i]);
  }

  console.log(`result of '${input}' is '${c.getResult()}'`);
}

Here is a simple test for the calculator using the exposed test function.

TestCalculator.ts

import { Calculator, test } from "./Calculator";


let c = new Calculator();
test(c, "1+2*33/11="); // prints 9

Now to extend this to add support for input with numbers in bases other than 10, let’s create ProgrammerCalculator.ts

ProgrammerCalculator.ts

import { Calculator } from "./Calculator";

class ProgrammerCalculator extends Calculator {
  static digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];

  constructor(public base: number) {
    super();
    if (base <= 0 || base > ProgrammerCalculator.digits.length) {
      throw new Error("base has to be within 0 to 16 inclusive.");
    }
  }

  protected processDigit(digit: string, currentValue: number) {
    if (ProgrammerCalculator.digits.indexOf(digit) >= 0) {
      return currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit);
    }
  }
}

// Export the new extended calculator as Calculator
export { ProgrammerCalculator as Calculator };

// Also, export the helper function
export { test } from "./Calculator";

The new module ProgrammerCalculator exports an API shape similar to that of the original Calculator module, but does not augment any objects in the original module. Here is a test for our ProgrammerCalculator class:

TestProgrammerCalculator.ts

import { Calculator, test } from "./ProgrammerCalculator";

let c = new Calculator(2);
test(c, "001+010="); // prints 3
doc_TypeScript
2016-10-04 19:25:31
Comments
Leave a Comment

Please login to continue.