Blog-Archiv

Dienstag, 22. Mai 2018

Call Overridables From Factory in TypeScript

You shouldn't call overridable methods from constructor. In my last Blog I demonstrated how this can lead to unexpected results due to uninitialized instance fields. In this Blog I want to show a way to circumvent this problem, without giving up the protected abstract initialization call to be implemented by sub-classes.

Replace constructor() by init()

The first step is to move the logic done in constructor to a protected init() instance-method. This init() is meant to be called by a static factory. To avoid the usage of (implicitly public) constructors from outside we define them all as protected, so that they can be called by their own class and sub-classes only. Here is the rewritten Name class:

Name.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
export abstract class Name
{
    private value: string;

    /** This is just to protect construction and allow factory only. */
    protected constructor() {
    }
    
    /** Object initialization, to be called by factory. */
    protected init(): void {
        this.value = this.getDefault();
        // all other initializations go here ...
    }

    /** Sub-classes must define a default. */
    protected abstract getDefault(): string;
    
    /** Expose the value readonly. */
    public getValue(): string {
        return this.value;
    }
}

The init() method now does what the constructor did before. The constructor is now proctected.

Add Static Factory

The rewritten concrete FirstName class provides the static factory. (This is NOT the "Factory Method" design pattern!)

FirstName.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Name } from "./Name.js";

export class FirstName extends Name
{
    /** Static factory replaces the constructor. */
    public static newFirstName(): FirstName {
        const firstName: FirstName = new FirstName();
        firstName.init();
        return firstName;
    }
    
    public readonly defaultValue: string = "(No Firstname)";

    /** This is protected to allow construction through factory only. */
    protected constructor() {
        super();  // compiler requires this!
    }
    
    protected getDefault(): string {
        return this.defaultValue;
    }
}

The public static newFirstName() is the factory. It calls the init() method after construction, so that all sub-classes must have finished their field-initializations. If you put all initializations inside that, there will be no problem any more with undefined fields.

When a factory is present, construction should not be possible from outside, thus all involved constructors must be protected or private.

Mind the implementation order: statics first, then instance fields and methods.

The bad news is that you have to repeat the static factory code on any concrete sub-class, e.g. LastName.

Test It

But before we think about that code duplication, let's test the new solution.

test.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { FirstName } from "./FirstName.js";

declare function title(assertTitle: string): void;
declare function assert(criterion: boolean, message: string): void;

title("Call Overridables From Factory");

const firstName: FirstName = FirstName.newFirstName();
assert(
    firstName.getValue() === firstName.defaultValue,
    "firstName.getValue() is expected to be '"+firstName.defaultValue+"': '"+firstName.getValue()+"'");

// following must NOT be possible when factory exists!
//const firstName2: FirstName = new FirstName();

Now the factory is used to create the FirstName object instance. The assertion makes sure that the default is estimated correctly.

When compiling the sources with

tsc -t ES6 *.ts

and running the test (in the test.html page I introduced in a passed Blog), we can see:

Call Overridables From Factory

firstName.getValue() is expected to be '(No Firstname)': '(No Firstname)'

Now the test is green!

Conclusion

Use a static factory whenever you need overrides already at construction time.

Is it possible to avoid the code duplication of the static factory? Maybe by using generics? There is even more code duplication, because both the factory and the init() method take over constructor responsibilities, therefore all construction parameters may have to be duplicated to both of them. Material for my next Blog.




Keine Kommentare: