The Practical Client

TypeScript 2.0: More Power for Defining Classes

The latest version of TypeScript provides developers with tools to create base classes and class hierarchies just like the other object-oriented languages.

In an earlier column, I looked at what TypeScript 2.0 adds in terms of improved data typing and how class discriminants let you signal with what class you're working. In this column, I'm going to look at what TypeScript 2.0 gives you when you're defining class hierarchies (base and derived classes). Here's a preview: It's what you would expect if you've worked with other object-oriented (OO) languages.

For me, the most important enhancement changes in TypeScript 2.0 are the introduction of abstract methods/properties and private/protected constructors in a class. Abstract methods and properties are a staple of other object-oriented languages and give you more power when defining the base type of a class hierarchy. The implementation in TypeScript is, however, sufficiently familiar to OO developers that I'm going to leave that for last and begin by looking at private and protected constructors.

One note: As of this article, I'm officially switching over to using the let keyword rather than the var keyword in my sample code. While the var keyword scopes a variable for the entire function that it's declared in, the let keyword scopes the variable for the block it's declared inside. Much of my programming experience is with languages that scoped variables in a more limited way than the JavaScript var keyword does -- in fact, it's pretty much the way that the let keyword scopes variables. Using let means that I can avoid a ton of errors that JavaScript is prey to and that I'm not smart enough to avoid automatically.

Implementing the Singleton Pattern
Private constructors give you the ability to implement classes that have control of their own creation because the only code that can instantiate the class is code inside the class. This allows you to, for example, implement the Singleton pattern where only one copy of an object can be created at a time. The Singleton pattern is used when creating an object is very expensive -- you only want to do it once -- or where it's important to have a single gatekeeper object that controls access to a resource (the page's IndexedDb database, for example).

In the Singleton pattern the convention is to use a method or property on the class with the name "instance" to handle creating and returning the class. The code in this member also typically stores the instantiated class internally so that the next time the method/property is used, the previously created copy can be returned. For this article, I'm creating a class called orderProcessor that holds all the functionality for processing Order classes. In this class, I'm defining a read-only property (that is, a property declared only with a get accessor) with a backing field called internalOrderProcessor to handle creating instances of the class.

In the orderProcessor's get accessor, I instantiate the orderProcessor class and store the resulting object in the property's backing field. Because a developer can't use the new keyword to instantiate the class, my property accessor has to be declared as static so that the property can be called directly from the class rather than an instance of the class. As a result, the backing field used by my property must also be declared as static so that it can be used by the static code in the property. And, finally, all references to my backing field must use the class name (rather than the this keyword, as would be the case if the class was instantiated) to work with the backing field.

Listing 1 shows the orderProcessor class with a method for running checks on the customer associated with the order. The class also has a property called processingLevel that controls the kinds of checks done on the customer that I'll be taking advantage of in the next section.

Listing 1: A Singleton Order Processing class
class orderProcessor {
  public processingLevel: string = "lax";
  private constructor() {  }

  private static bfInternal: orderProcessor;
  static get Instance(): orderProcessor {
    if (orderProcessor.bfInternal == undefined) {
      orderProcessor.bfInternal = new orderProcessor();
    }
    return orderProcessor.bfInternal;
  }
  checkCustomer(cust: Order): boolean {
    // ...Code to check customer using processingLevel and returning true/false
  }
}

A developer using this class might write code like this:

let op: orderProcessor = orderProcessor.Instance;
op.processingLevel = "strict";
let ord: Order = new Order("A123");
let ok: boolean = op.checkCustomer(ord);

With this design, a client that repeatedly reads the orderProcessor's instance property will always get the same copy of orderProcessor. One note, though: Classes with private constructors cannot be extended.

Creating Base Classes
If you declare your constructor as protected, on the other hand, while your class still cannot be instantiated from external code, it can be extended. A protected constructor can be used by code within the class (like the private constructor) and also by code in a class that extends it. Listing 2 shows a new orderProcessorBase class that has no static members and a protected constructor. With a protected constructor (and no static methods) this class can't be used directly by client code…but it can be used as the base for other classes.

Listing 2: A Base orderProcessor Class
class orderProcessorSingletonBase {
  private level: string;
  protected constructor() {
  }

  private bfInternal: orderProcessorSingletonBase;
  public processingLevel: string = "lax";
  public get Instance(): orderProcessorSingletonBase {
    if (this.bfInternal == undefined) {
      this.bfInternal = new orderProcessorSingletonBase();
    }
    return this.bfInternal;
  }
  checkCustomer(ord: Order): boolean { … }
}

When defining a new class that extends orderProcessorBase I call the orderProcessorBase's constructor through the super method in my new, extended class's constructor. Using the super reference, I can also configure the orderProcessorBase's level property from my extended class's constructor (or any other public/protected member of my extended class, for that matter).

This orderProcessorStrict class configures an orderProcessBase class for strict processing of Custoemer data:

class orderProcessorStrict extends orderProcessorSingletonBase {
  constructor() {
    super();      
   this.processingLevel = "strict";
 }
}

A developer using my new, extended class could now write code like this:

let opm: orderProcessorStrict;
opm = new orderProcessorStrict();
let ord: Order = new Order("A123");
let ok: boolean = opm.checkCustomer(ord);

Abstract Members and Classes
Abstract members are another way to create base classes that cannot be used directly by client code -- they exist purely for the purpose of creating new, extended classes.

This code defines an abstract orderProcessBase class with a single abstract method:

abstract class orderProcessorBase {
  protected constructor() {
  }
  abstract checkCustomer(ord: Order): boolean
}

Using that as a base, Listing 3 shows classes that extend orderProcessorBase to create classes with very strict and very lax processing by overriding the checkCustomer method.

Listing 3: Extended Classes Based on an Abstract Class
class orderProcessorLax extends orderProcessorBase {
  protected constructor() {
    super();
  }
  checkCustomer(ord: Order): boolean { return true }
}
class orderProcessorStrict extends orderProcessorBase {
  protected constructor() {
    super();
  }
  checkCustomer(ord: Order): boolean { return false }
}

As I said, this functionality won't be new to developers with experience in other object-oriented languages. But it does elevate TypeScript to parity with all of those other object-oriented languages … except they can't run inside a browser and TypeScript can.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube