Rule(s)
- Although JavaScript 5 is not an OO programming language (no direct support for inheritance and polymorphism), OO principles may apply through a disciplined design (see also ☛).
- JavaScript 5 leads us to implement objects case-by-case.
Example (old fashion) Temperature.js.zip
var temperature = { // The notion of temperature Min: -273.15, // in Celsius _value: 0, // in Celsius _step: 0.0001, asCelsius: function () { return this._value; } // Etc. };
Example (JavaScript 6 alternate syntax) Temperature.js.zip
var temperature = { // The notion of temperature Min: -273.15, // in Celsius _value: 0, // in Celsius _step: 0.0001, asCelsius() { // Not so clear because confusing with a call of 'asCelsius'... return this._value; } // Etc. };
Rule(s)
- Instead, using OO programming, the concept of constructor may improve design.
Example (better, but still not up-to-date) Temperature.js.zip
var Temperature_unit = { Celsius: 0, Fahrenheit: 1, Kelvin: 2 }; var Invalid_temperature_exception = function (value) { // Exception type this._message = "Invalid temperature"; this._value = value; }; var Temperature = function (value, unit) { this.Min = -273.15; // in Celsius this._value = 0; // in Celsius switch (unit) { case Temperature_unit.Celsius: this._value = value; break; case Temperature_unit.Fahrenheit: this._value = (value - 32.) * 5. / 9.; break; case Temperature_unit.Kelvin: this._value = value + this.Min; break; default: throw "Illegal temperature unit"; } if (this._value < this.Min) { throw new Invalid_temperature_exception(this._value); } this._step = 0.0001; this.asCelsius = function () { return this._value; }; this.asFahrenheit = function () { return 9. / 5. * this._value + 32.; }; // Better: Object.defineProperty(this, "asKelvin", {value: function () { return this._value - this.Min; }, enumerable: true, configurable: false, writable: false} ); // Etc. };
Example (archetype)
const Dish = function (main_ingredient) { this.ingredients = []; this.main_ingredient = main_ingredient; this.add_ingredient = function (ingredient) { this.ingredients.push(ingredient); }; }; const lasagna = new Dish(Symbol("Pasta")); console.log(lasagna.main_ingredient); // 'Symbol(Pasta)' is displayed... lasagna.add_ingredient(Symbol("Cheese")); console.assert(lasagna.constructor === Dish); console.assert(Object.getPrototypeOf(lasagna) === Dish.prototype); // "Prototype inheritance"... console.log(Dish.prototype.constructor); // Constructor function (code is displayed)... console.assert(Dish.prototype.constructor.name === "Dish");
Rule(s)
- Taking advantage of objects' prototype prevents code duplication.
Example Temperature.js.zip
Object.defineProperty(Temperature.prototype, "Min", {value: -273.15, enumerable: true, configurable: false, writable: false}); Temperature.prototype.asCelsius = function () { return this._value; }; Temperature.prototype.asFahrenheit = function () { return 9. / 5. * this._value + 32.; }; // Better: Object.defineProperty(Temperature.prototype, "asKelvin", {value: function () { return this._value - this.Min; }, enumerable: true, configurable: false, writable: false} ); // Etc.
Rule(s)
- Caution: constructors cannot be defined from lambda expressions.
Example
var Pair = (first, second) => { this.first = first; this.second = second; }; const p = new Pair('a', 'z'); // Bug: 'TypeError: Pair is not a constructor' Pair('a', 'z'); // Bug: 'this' is 'undefined'!
Rule(s)
- Caution: the same applies for member functions (written with lambda expressions) because
this
is unbound to the “current” object.Example
var Pair = function (first, second) { this.first = first; this.second = second; }; Pair.prototype.getFirst = () => { return this.first; }; const p = new Pair('a', 'z'); // This works now... window.alert(p.getFirst()); // Bug: 'this' is 'undefined'!
Example of inheritance tree (in French)
Rule(s)
- Inheritance is simulated through the chaining of prototypes. Further detail about the inner architecture of JavaScript non-primitive objects is ☛.
Example Inheritance_polymorphism.ts.zip
var Compte_bancaire = function (id, solde) { this._id = id; this._solde = solde; this._cumul_interets; }; Compte_bancaire.Information = function () { return "Classe générale des comptes bancaires"; }; Compte_bancaire.prototype.id = function () { return this._id; }; Compte_bancaire.prototype.solde = function () { return this._solde; }; Compte_bancaire.prototype.cumul_interets = function () { return this._cumul_interets; }; Compte_bancaire.prototype.mise_a_jour = function (montant) { this._solde += montant; return this._solde; }; Compte_bancaire.prototype.taux_interet = function () { throw "Undefined function due to abstract nature of the class..."; }; Compte_bancaire.prototype.appliquer_taux_interet = function () { this._cumul_interets = this._solde * (1. + (this.taux_interet() / 100.)); }; Compte_bancaire.prototype.compareTo = function (cb) { return this._solde > cb._solde ? 1 : this._solde < cb._solde ? -1 : 0; }; var Compte_cheque = function (id, solde, taux_interet, seuil) { Compte_bancaire.call(this, id, solde); // 'super' in Java this._taux_interet = taux_interet; this._seuil = seuil; }; Compte_cheque.prototype = Object.create(Compte_bancaire.prototype); // Inheritance link Compte_cheque.prototype.constructor = Compte_cheque; Compte_cheque.Information = function () { return "Classe des comptes bancaires courants ou \"comptes chèque\" - rémunération à la tête du client !"; }; Compte_cheque.prototype.taux_interet = function () { return this._taux_interet; }; Compte_cheque.prototype.appliquer_taux_interet = function () { if (this._solde > this._seuil) Compte_bancaire.prototype.appliquer_taux_interet.call(this); // 'super' in Java };
Rule(s)
- Polymorphism is operable at run-time.
Example Inheritance_polymorphism.ts.zip
console.log(Compte_bancaire.Information()); let cb = null; try { cb = new Compte_bancaire("cb", 100.); cb.appliquer_taux_interet(); // This fails since 'Compte_bancaire' is implemented as an abstract class console.log(cb.cumul_interets()); } catch (e) { console.log(e); } console.log(Compte_cheque.Information()); cb = new Compte_cheque("cc", 200., 0.02, 100.); cb.appliquer_taux_interet(); console.log(cb.cumul_interets());
Rule(s)
- Inheritance and polymorphism are natively supported from JavaScript 6 (ECMAscript 2015) with the introduction of dedicated keywords:
class
,constructor
,static
,extends
, andsuper
.Example (archetype)
class Dish_ { // From JavaScript 6... constructor(main_ingredient) { this.ingredients = []; this.main_ingredient = main_ingredient; } add_ingredient(ingredient) { this.ingredients.push(ingredient); }; } const lasagna_ = new Dish_(Symbol("Pasta"));
Rule(s)
static
is used for class methods. About class attributes, it is only supported from JavaScript 7 (or later versions depending on the browser).Example (
static
attribute in JavaScript 7)class Temperature { static Min = -273.15; // in Celsius … }
Rule(s)
- The idea of static initializer (that is native in Java) is a handy mechanism for “global” data initialization, i.e., the initialization of static class attributes. It can be simulated in JavaScript 6.
Example
class NoLanguageSettings { … static Universe_radius = 3000; static Half_sphere_geometry = new THREE.SphereBufferGeometry(NoLanguageSettings.Universe_radius / 25, 30, 30, 0, Math.PI); static _Initializer = (() => { NoLanguageSettings.Half_sphere_geometry.computeBoundingSphere(); })(); // <- Caution: automatic call... … }
Example (full inheritance hierarchy) Inheritance_polymorphism.ts.zip
class Compte_bancaire_ { constructor(id, solde) { this._id = id; this._solde = solde; this._cumul_interets; } static Information() { return "Classe générale des comptes bancaires"; } id() { return this._id; } solde() { return this._solde; } cumul_interets() { return this._cumul_interets; } mise_a_jour(montant) { this._solde += montant; return this._solde; } taux_interet() { throw "Undefined function due to abstract nature of the class..."; } appliquer_taux_interet() { this._cumul_interets = this._solde * (1. + (this.taux_interet() / 100.)); } compareTo(cb) { return this._solde > cb._solde ? 1 : this._solde < cb._solde ? -1 : 0; } } class Compte_cheque_ extends Compte_bancaire_ { constructor(id, solde, taux_interet, seuil) { super(id, solde); this._taux_interet = taux_interet; this._seuil = seuil; } static Information() { return "Classe des comptes bancaires courants ou \"comptes chèque\" - rémunération à la tête du client !"; } taux_interet() { return this._taux_interet; } appliquer_taux_interet() { if (this._solde > this._seuil) { super.appliquer_taux_interet(); } } }
Rule(s)
- Polymorphism is controllable at run-time only.
Example Inheritance_polymorphism.ts.zip
console.log(Compte_bancaire_.Information()); let cb_ = null; try { cb_ = new Compte_bancaire_("cb", 100.); cb_.appliquer_taux_interet(); // This fails since 'Compte_bancaire' is implemented as an abstract class... console.log(cb_.cumul_interets()); } catch (e) { console.log(e); } console.log(Compte_cheque_.Information()); cb_ = new Compte_cheque_("cc", 200., 0.02, 100.); cb_.appliquer_taux_interet(); console.log(cb_.cumul_interets());
OO programming in TypeScript Rule(s)
- Like JavaScript 6, TypeScript uses the
class
keyword to create data structures. Beyond, contrary to JavaScript 6, TypeScript natively supports the notion ofinterface
.- By construction, the key goal of TypeScript is the typing of objects in order to support static type checking at compilation time. So, data structures as classes introduce new types.
Example (archetype)
class Dish { private ingredients: Array<symbol> = new Array(); private main_ingredient: symbol; public constructor(main_ingredient: symbol) { this.main_ingredient = main_ingredient; } public add_ingredient(ingredient: symbol) { this.ingredients.push(ingredient); }; } const lasagna = new Dish(Symbol("Pasta"));
Classes
Rule(s)
private
andreadonly
used as modifiers for method arguments only work for constructors.Example N_INSEE-SweetAlert2.ts.zip
class N_INSEE { constructor(private readonly _n_insee: number, private readonly _clef: number) { // Constructor is by default 'public'... // 'this._n_insee' is automatically inserted as private field // 'this._clef' is automatically inserted as private field } calcul_clef(): boolean { // Any method is by default 'public'... if ((97 - (this._n_insee % 97)) === this._clef) return true; else return false; } } const main = function () { // 'window.onload = main;' let n_insee: N_INSEE = new N_INSEE(1630125388055, 29); if (n_insee.calcul_clef()) window.alert("OK"); else window.alert("Non OK"); }
Example (JavaScript 6 code generated by TypeScript compiler)
class N_INSEE { constructor(_n_insee, _clef) { this._n_insee = _n_insee; this._clef = _clef; } calcul_clef() { if ((97 - (this._n_insee % 97)) === this._clef) return true; else return false; } } const main = function () { // 'window.onload = main;' let n_insee = new N_INSEE(1630125388055, 29); if (n_insee.calcul_clef()) window.alert("OK"); else window.alert("Non OK"); };
Getter and setter
Rule(s)
- TypeScript introduces a support for getter and setter functions by means of the
get
andset
keywords. Note that getting, respectively setting, values does not involve the syntactical use ofget
, respectivelyset
.- A getter and a setter may be
static
.Example (use of getter)
private _focus: boolean = false; get focus(): boolean { return this._focus; } set focus(focus: boolean) /* : void */ { // TypeScript setter rejects returned type... this._focus = focus; } … if (object_with_focus.focus) … // Getter call is without parentheses...
Example (use of setter)
private _camera = Camera.General; get camera(): Camera { return this._camera; } set camera(camera: Camera) { this._camera = camera; } … this.camera = Camera.Embedded; // Setter call is without parentheses...
static
propertyRule(s)
- Class attributes and methods may be declared
static
. One may in particular notice that JavaScript 6 supportsstatic
methods whilestatic
attributes are a feature of JavaScript from ver. 7.Example Inheritance_polymorphism.ts.zip
abstract class Compte_epargne extends Compte_bancaire { protected static _Taux_interet: number; … } class Livret_A extends Compte_epargne { public static Information() { return "Classe des livrets A - rémunération fixée par l'Etat !"; } public static Initialize() { Compte_epargne._Taux_interet = 0.5; // 13/02/2020 } }
Rule(s)
- Similar to Java, the notion of
static
initializer requires TypeScript ver. 4.4.x. Aged versions require simulation by means of the concept of “decorator”, which, in TypeScript ver. 3.7, remains an experimental feature.Example Inheritance_polymorphism.ts.zip
/** Simulation of Java static initializer */ function Initialize(target: any) { // Decorator... target.Initialize(); } … @Initialize // '"experimentalDecorators": true,' class Compte_epargne_logement extends Compte_epargne { public static Information() { return "Classe des comptes épargne logement - rémunération ratio du Livret A !"; } public static Initialize() { // Launched by decorator... Compte_epargne._Taux_interet = Livret_A._Taux_interet * 2. / 3.; // 2/3 du taux du Livret A... // Compte_epargne._Taux_interet = ...; // arrondi au 1/4 point le plus proche (0.5 * 2. / 3. ==== 0.33) -> (0.25)... } }
Optional property
Rule(s)
- Attributes and methods may be declared optional by means of the
?
operator. This may occur both in classes and interfaces.Example (attribute in class)
protected _eyes?: Array<any>; protected _face: Array<any> | undefined; // <=> 'protected _face?: Array<any>;' protected _mouth?: Array<any>; … if (this._eyes !== undefined) … // Required by the compiler with '"strict": true,'!
Example (attribute in class with simplified access based on the
?
operator)private _my_friends?: Array<Person>; … this._my_friends?.forEach(…); // Required by the compiler with '"strict": true,'!
Example (method in class)
protected _compute_morphing?(geometry: any): void; … if (this._compute_morphing) // Required by the compiler with '"strict": true,'! this._compute_morphing(front_geometry);
Constructor overloading
Rule(s)
- Compared to C++ or Java, TypeScript does not support constructor overloading in a direct way.
Example
class Individual { protected readonly _birth_date: number; constructor(readonly birth_date: string) { this._birth_date = Date.parse(birth_date); } // private constructor(readonly birth_date: number) { // No other constructor is accepted, even 'private'! // this._birth_date = birth_date; // } // cloning(): this { // This differs from 'Current' polymorphic type in Eiffel! // return this; // Only 'this' object complies with 'this' *TYPE*! // } cloning(): Individual { console.log("cloning in Individual...\n"); // return new Individual(this._birth_date); // No way because multiple constructors aren't accepted! return new Individual(new Date(this._birth_date).toDateString()); } }
Method overloading
Rule(s)
- Method overloading is possible in TypeScript provided that appropriate strategies are performed. For methods that do not have the same number of arguments (see also ☛), default arguments' values may help.
Example (default values of arguments)
private _sobel_post_processing(canvas: HTMLCanvasElement, threshold: number = 100) { …
Example (problem)
get_texture(): any { // Compilation error resulting from conflict... return this._textures.get(NoLanguageCharacter_texture_type.Normal); } get_texture(type: NoLanguageCharacter_texture_type): any { // Compilation error resulting from conflict... return this._textures.get(type); }
Example (solution)
public get_texture(type: NoLanguageCharacter_texture_type = NoLanguageCharacter_texture_type.Normal): any { return this._textures.get(type); }
Rule(s)
- In the most common case, overloads yield the separation between specifications and (unique) implementation.
Example (solution)
// Overloads => multiple signatures without implementation: public static Delete(motive: Mongoose.LeanDocument<Motive>): Promise<Mongoose.LeanDocument<Motive>>; public static Delete(motive_number: string): Promise<string>; // Implementation by means of union types: public static Delete(m: Mongoose.LeanDocument<Motive> | string) { return typeof m === "string" ? MotiveModel.deleteOne({motive_number: m}).exec() : MotiveModel.deleteOne(m).exec(); }
Example (using conditional types…) Conditional_type.ts.zip
class A { constructor(readonly a: number) { } } class B { constructor(private readonly _b: string) { } } function f(n: number): A; // Overloading (spec.) // Evite : 'const r2: A = <A>f(0);' function f(s: string): B; // Overloading (spec.) function f(x: number | string): A | B { // Implementation return typeof x === "number" ? new A(x) : new B(x); } // const r1: B = f(0); // Compilation error... const r2: A = f(0); // OK // Le type 'X' est défini comme conditionnel, il est générique paramétré par 'T' compatible à 'number' ou 'string' : type X<T extends number | string> = T extends number ? A : B; // La fonction 'g' est générique paramétrée par 'T'compatible à 'number' ou 'string' : function g<T extends number | string>(x: T): X<T> { return typeof x === "number" ? new A(x) as X<T> : new B(x) as X<T>; // Horrible mais bon... } // const a_: B = f(0); // Compilation error... const a_: A = g(0); // OK type Y<T extends { a: unknown }> = A["a"]; const y: Y<A> = 0; // Le type de 'y' est 'number'... // const z: Y<B> = true; // Compilation error...
Inheritance in TypeScript
Rule(s)
- TypeScript supports single inheritance only. As in Java, the
super
keyword allows the call of a direct ancestor's constructor.Example Inheritance_polymorphism.ts.zip
abstract class Animal {…} abstract class Mammal extends Animal {…} abstract class Oviparous extends Animal {…} // class Ornithorhynchus extends Mammal, Oviparous { // Not supported while it is in C++! // } class Giraffe extends Mammal { constructor(private readonly _name: string, private readonly _height: number) { super(); // Mandatory } }
Rule(s)
- TypeScript supports abstract methods whose visibility cannot be private.
- In practice, private methods cannot be overridden while an overridden protected method can become public in a subclass.
Example
export default abstract class NoLanguageCharacter { protected abstract _hex_color(): number; // No code... …
Example
export default class Troublemaker extends NoLanguageCharacter { // No longer abstract... protected _hex_color(): number { // Cannot "become" 'private' while it may become public... return …; } …
Rule(s)
- Constructor delegation stumbles over the use of
private readonly
.Example
abstract class Friend { protected constructor(private readonly _nickname: string) { } } class Close_friend extends Friend { // 'private readonly' tries the creation of another '_nickname' private attribute! public constructor(_nickname: string) { super(_nickname); } }
Rule(s)
- Abstract methods can be optional.
Example
interface Responsibility { /* ... */ } abstract class Individual { abstract responsibilities?(): Set<Responsibility>; // Abstract and optional... abstract isResponsible(responsibility: Responsibility): boolean; } class Kid extends Individual { responsibilities: undefined; // Implementation allows 'Kid' to no longer be abstract... isResponsible(responsibility: Responsibility): boolean { return false; // 'false' in all cases: 'responsibility' is ignored... }; }
Polymorphism in TypeScript
Rule(s)
- Polymorphism applies in TypeScript including covariance on return types.
Example Inheritance_polymorphism.ts.zip
class Female extends Individual { cloning(): Female { console.log("cloning in Female...\n"); return new Female(new Date(this._birth_date).toDateString()); } } … // Later on: let Eve: Individual = new Female("0, 0, 0"); let Eve_clone: Individual = Eve.cloning(); // 'cloning in Female...' is displayed => covariance applies in TypeScript!
Using
this
as a typeExample
class Individual { … that_s_me(): this { // This differs from 'Current' polymorphic type in Eiffel! return this; // Only 'this' object complies with 'this' *TYPE*! } } … // Later on: console.assert(Eve.that_s_me().constructor === Female); // 'true' is displayed!
Using
this
in conjunction withbind
Example
class My_class { private readonly _resolve_2020: Promise<number> = Promise.resolve(2020); … this._resolve_2020.then(function (this: My_class, value: number) { // Differently to JavaScript, TypeScript makes explicit the type of 'this' in the function... console.assert(value === 2020); // Use 'this' as effective instance of 'My_class'... }.bind(this));
Example
interface Person { identities: Array<string>; display?: Function; } const person: Person = { identities: ["Barbier", "Franck", "Bab"], display: function (this: Person) { // Differently to JavaScript, TypeScript makes explicit the type of 'this' in the function... console.assert(person === this); this.identities.forEach(function (element) { window.alert(element); }.bind(this)); // This guarantees 'console.assert(person === this)' } }; person.display();
interface
keywordRule(s)
- Like Java, TypeScript introduces the concept of interface as a contract. An interface is a type with properties. Any instance of this type owns these properties. Note that TypeScript interfaces have no static stuff: fields in particular, unlike Java, are instance fields.
- A remaining question is: what is the key difference between interfaces and abstract classes in TypeScript? Discussion ☛ demonstrates that interfaces are compilation-only constructions. Practically,
instanceof
cannot be used with interfaces while it works for abstract classes. Moreover, abstract classes may have concrete features, i.e., fields with values (to-be-complete structure) or methods with bodies while other methods are abstract (no body).Example
export enum Access { // 'export' allows external usage Failure, Success, } export interface Image_access { access: Access; image_URL: string; } export interface Open_Food_Facts_item { images: Array<Promise<Image_access>>; image_URLs: Array<string>; product_name: string; } … // Somewhere else: private readonly _item_data: Promise<Open_Food_Facts_item>;
Functional interface
Rule(s)
- An interface may play the role of a function type.
Example
interface Main { // Similar to Java "functional interface" -> "function type" in TypeScript (): void; // Call signature } … // Somewhere else: const main: Main = () => { (…) } // Ellipses embody concretization
Note(s)
- Advanced issues of function types are discussed ☛.
Interface concretization
Rule(s)
- As “contracts”, interfaces declare features, which may be potential attributes or abstract methods. Attributes can be lambda expressions as well; As common methods, lambda expressions have to be equipped with a body to make, if desired, classes concrete (as opposed to abstract).
Example
interface Currency { // Example of USA dollar: common_symbol: string; // $ common_name: string; // Dollar description?: string; iso_code: number; // 840 iso_symbol: string; // USD substitute?: Currency; // 'undefined' substitution_date: number | null; } class Currencies { public static readonly Currencies: Array<Currency> = [ { common_symbol: "$", common_name: "Dollar", description: "First leading currency in the world...", iso_code: 840, iso_symbol: "USD", substitution_date: null }, { common_symbol: "€", common_name: "Euro", description: "Second leading currency in the world...", iso_code: 978, iso_symbol: "EUR", substitution_date: null } ]; } interface Iso_code_checking { // Interface as contract... already_exists: (iso_code: number) => Promise<boolean>; // Attribute as lambda expression... } class Iso_code_checking_service implements Iso_code_checking { // Class as service... already_exists(iso_code: number): Promise<boolean> { // Concretization... const exists = Currencies.Currencies.find(currency => currency.iso_code === iso_code); // Check on the Web as well.... return Promise.resolve(exists !== undefined ? true : false); } }
Rule(s)
- Concretization may operate in a minimalist way for “or” types.
Example
interface AsyncValidator { validate(ac: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>; } … class My_asynchronous_validator implements AsyncValidator { validate(ac: AbstractControl): Promise<ValidationErrors | null> { /* Concrete code here... */ } }
Generic interface
Rule(s)
- The common way of reusing an interface in TypeScript is, like Java, its implementation (
implements
keyword) by a class.Example Palindrome.ts.zip
interface Pair<T> { readonly _first: T; // Cannot be 'private'... readonly _second: T; description?: string; } class Palindrome_authorized_interval implements Pair<string> { // Caution: attributes from interface *MUST* be repeated here... // This is done through constructor: constructor(readonly _first: string, readonly _second: string) { } /* 'private' -> error because 'public' in 'Pair<T>' */ description: string; // May be omitted because optional in 'Pair<T>' set_description(/* 'readonly' -> constructor only! */ description: string): void { // 'public' by default this.description = description; } }
Interface inheritance
Rule(s)
- As for Java, interfaces may inherit (
extends
keyword) from others (multiple inheritance).Example
// 'CustomEvent' comes from the 'DOM' library (https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent): interface My_event_type extends CustomEvent { additional_attribute?: symbol; }
Rule(s)
- Interface inheritance may lead to the contractual “transfer” of, as shown before, attributes, but methods as well.
Example Palindrome.ts.zip
enum Comparable_ {LessThan, Equal, GreaterThan} interface Comparable<T> { // Similar to Java "functional interface" compareTo(t: T): Comparable_; // Method signature } interface Pair<T> extends Comparable<Pair<T>> { readonly _first: T; // Cannot be 'private'... readonly _second: T; description?: string; }
Example (abstract nature of
Palindrome_authorized_interval
class is removed…) Palindrome.ts.zipclass Palindrome_authorized_interval implements Pair<string> { … compareTo(t: Pair<string>): Comparable_ { // 'public' by default if (this._first.localeCompare(t._first) === -1 && this._second.localeCompare(t._second) === -1) return Comparable_.LessThan; if (this._first.localeCompare(t._first) === 0 && this._second.localeCompare(t._second) === 0) return Comparable_.Equal; if (this._first.localeCompare(t._first) === 1 && this._second.localeCompare(t._second) === 1) return Comparable_.GreaterThan; } … }
Example (abstract nature of
Complex
class is kept…)abstract class Complex implements Pair<number> { constructor(readonly _first: number, readonly _second: number) { } abstract compareTo(t: Pair<number>): Comparable_; … }
Rule(s)
- Type guard may operate in conjunction with interface inheritance.
Example
interface Man { children?: Array<Individual>; } interface Father extends Man { children: Array<Individual>; // Compilation time... } function isFather(man: Man): man is Father { return 'children' in man && man.children!.length > 0; // Execution time... } function elder(man: Man): Individual { if (isFather(man)) return man.children[0]; // No compilation error! }