I like Typescript
After a year of working with Typescript I could do away with every other language for ever. Working in one language from the browser though to the backend has been fantastic. To say I am a fan would be an understatement. Being able to transpile my TS objects into a JS compliant ES5 or ES6 code way has been a huge load off.
For those of you who haven’t tried it, Typescript is a superset of JavaScript which still feels and acts like Javascript. However it adds additional types, generics, classes, abstract classes, interfaces and modules to provide a full object oriented scripting language. For larger applications beyond a few hundred lines, this is an incredible step forward for developing maintainable and robust code bases. If you can JavaScript, you can Typescript.
I honestly can not understand why a Javascript developer would attempt to develop a new system without it. The productivity gains and type safety alone are worth it. How’s that for conviction?
An example
I am building a hypothetical system which switches one or more lamps. In this post I am going to build a simple Switchable
type which is implemented by two concrete classes Lamp
and DimmableLamp
. Although the example is simple, it demonstrates a simple type hierarchy that utilises polymorphism not unlike something that you would use in a real environment. Typescript looks VERY much like JavaScript/Java/C#. Nice!
Interface or Abstract Class
In this example I have used an interface to define my Switchable type. This is a simple example where an abstract class was not required but could have just as easily been employed like this:
abstract class BaseSwitchable implements Switchable {
flickSwitch() {
// change state here...
}
abstract doSomething(): void;
}
The advantage of an interface is that you have a contract which any class can conform to. The advantage of an abstract class is that you can implement common methods whilst leaving the developer to code there own implementation for others. In the code snippet above, the flickSwitch()
method is common whilst the doSomething()
method is abstract and will require the developer to implement it. In other words, abstract classes define behaviour whereas interfaces simply provide a contract. If you’re unsure, just do a web search.
Switchable interface
The Switchable interface is out base type from which our concrete types are implemented. It provides some simple properties as you would expect AND the main flickSwitch()
method for switching the lamp.
Lamp implementation
Lamp
is a concrete class which implements the Switchable
interface. After constructing the object we our lamp is ready to switch.
DimmableLamp sub class
DimmableLamp
extends Lamp
and thus it is also of type Lamp
and Switchable
. This subclass class adds the set voltage()
method to dim the lamp. In true polymorphic style, as a sub-type of Lamp
, DimmableLamp
object can be used in much the same way as a Lamp
object. However, a Lamp
can not be used as a DimmableLamp
.
Kelvin Enum
Enums probably need a separate post in their own right. In short,Enums provide a nice way of self documenting numeric values by defining a set of named to apply to numeric constraints. The mapping is two-way.
For example, this demonstrates the return value of an Enum where no values have been assigned to the keys. Each value is computed.
enum Size {
Big, // 0
Medium, // 1
Small // 2
}
console.log(Size.Big)
=> 0
Although computed Enums are zero-based, keys can be assigned a number value of your choice on an(any/all) item(s). This example demonstrates an enum with both assigned and computed values:
enum Size {
Big = 5, // 5
Medium, // 6
Small = 23, // 23
Tiny // 24
}
console.log(Size.Medium, Size.Small, Size.Tiny)
=> 6, 23, 24
With Typescript 2.4 and beyond this requirement has been expanded to include other objects.
Union types
Typescript introduces a very nice feature of string literals. That is:
<span class="pl-k">private</span> state<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>on<span class="pl-pds">"</span></span> <span class="pl-k">|</span> <span class="pl-s"><span class="pl-pds">"</span>off<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>off<span class="pl-pds">"</span></span>;
This means that the variable state
can only have the strings on
or off
assigned to it. This is incredibly useful when creating self documenting code. Although the constraint is not compiled into JavaScript, the compiler will catch any assignments that do not conform to this contract.
Access modifiers
In this example we have a number of variables. The default access modifier is public
for anything where the access modified is not specified. Anything market public
is accessible from anywhere.
When a member is marked private
, it cannot be accessed from outside of its containing class. The protected
modifier acts much like the private
modifier with the exception that members declared protected
can also be accessed by instances of deriving classes.
Be aware that when comparing types that have private
and protected
members, Typescript treats these types differently. For two types to be considered compatible, if one of them has a private
member, then the other must also have a private
member that originated in the same declaration. The same applies to protected
members.
Like JavaScript, TypeScript supports getters/setters as a way of intercepting accesses to a member of an object. This gives you a way of having finer-grained control over how a member is accessed on each object.
So what does it all look like?
This code example below provides a good view of what a a basic class heirarchy could look like.
/**
* A class demonstrates how the Switchable objects can be used in an OO fashion.
*/
export class Switcher {
private switchables: Switchable[] = [];
constructor () {
this.add(new Lamp("Osram", Kelvin.warm));
this.add(new DimmableLamp("GE", Kelvin.warm));
this.add(new Lamp("Phillips", Kelvin.cool));
this.add(new Lamp("GE"));
}
switchAll(): void {
this.switchables.forEach(switchable => {
switchable.flickSwitch();
})
}
add(switchable: Switchable): void {
this.switchables.push(switchable);
}
getSwitchables(): Switchable[] {
return this.switchables;
}
}
/**
* The Switchable interface to be implemented by our Lamp classes. A basic set of properties and
* one method to be implemented.
*/
export interface Switchable {
brand: string;
warmth: Kelvin;
voltage: number;
state: "on" | "off";
flickSwitch(): void;
}
/**
* The implementation of Switchable - a Lamp which can be switched on/off.
*
* This class is of type Lamp and Switchable.
*/
export class Lamp implements Switchable{
protected voltage: number = 220;
private state: "on" | "off" = "off";
private brand: string;
warmth: Kelvin;
constructor(lampBrand: string, warmth?: Kelvin) {
this.brand = lampBrand;
this.warmth = (warmth === undefined) ? Kelvin.neutral : warmth;
}
flickSwitch() {
this.state = this.changeState();
}
get state(): number {
return this.state;
}
get voltage(): number {
return this.voltage; // immutable
}
private changeState(): "on" | "off" {
if (this.state === "on") {
return "off";
} else {
return "on";
}
}
}
/**
* The extended Lam class - a Lamp which can be switched on/off AND dimmed.
*
* This class is of type DimmableLamp, Lamp and Switchable.
*/
export class DimmableLamp extends Lamp {
constructor(lampBrand: string, warmth?: Kelvin) {
super(lampBrand, warmth);
}
set voltage(voltage: number) {
this.voltage = voltage; // mutator
}
}
/**
* An enumeration which represents the Kelvins (i.e. warmth) of the switchable item.
*/
export enum Kelvin {
warm = 2700,
neutral = 3800,
cool = 4500
}
« Assertions
LinkedList vs ArrayList in Java »