Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Shvejan Shashank
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Shvejan Shashank

Static Methods Can Access Private Class Constructors In TypeScript

By
Published in Comments (2)

Yesterday, in a TypeScript lunch-n-learn that Rob Eisenberg was leading here at InVision, we started talking about various ways in which we could make class Constructors impossible to invoke from outside of the Class context. This got me thinking about Static methods; and, their ability to access private instance propertiers. It has already blown my mind that one class instance can access the private variables of another class instance. So, I started to wonder if Static methods were granted the same kind of access. And, if so, could they be used to invoke private constructors on their own class. Spoiler alert: they are and they can.

The cool thing about the "implements" directive in TypeScript is that it can point to both Interfaces and Classes. Meaning, one Class can be defined as implementing the API of an existing class. However, unlike an Interface, a Class definition can have private properties. And, if you go to implement that Class, you have to include the same private properties in your implementation. If you don't, TypeScript will throw an error.

class A {
	private message: string;
}

class B implements A {
	// ...
}

As you can see in this code, Class B is implementing Class A, which has a private property. Now, if we try to run this TypeScript through ts-node, we get the following error:

Unable to compile TypeScript
Class 'B' incorrectly implements class 'A'. Did you mean to extend 'A' and inherit its members as a subclass? Property 'message' is missing in type 'B'. (2720)

At first, this seems counter-intuitive - why would TypeScript even care about private implementation details? After all, isn't that the whole point of encapsulation? That we can change the private implementation as we need to without breaking the API contract?

This is one feature of Class design that really confused me due to my lack of experience with Object Oriented Programming (OOP) languages. It turns out that private variables are technically part of the Class API - but only locally to the Class. What this means is that while no one else outside of the Class can access the private properties or Class "A", one instance of class "A" can actually access the private properties of another instance of Class "A".

Mind blown!

Given this private property accessibility, I wanted to see if Static method on a Class could access the private properties of a Class instance. Specifically, I wanted to see if Static "factory methods" could invoke a private constructor, forcing all Class instantiation to go through the static methods. To test this, I created a Person class with a private constructor and several static factory methods:

interface NameParts {
	first: string;
	middle?: string;
	last: string;
}

class Person {

	private first: string;
	private middle: string;
	private last: string;

	// I initialize the Person class with the given name.
	// --
	// CAUTION: This constructor is PRIVATE - you must use one of the static factory
	// methods in order to instantiate the class.
	private constructor( first: string, middle: string, last: string ) {

		this.first = first;
		this.middle = middle;
		this.last = last;

	}

	// ---
	// PUBLIC METHODS.
	// ---

	public toString() : string {

		var printableParts = [ this.first, this.middle, this.last ].filter(
			( part: string ) : boolean => {

				return( !! part );

			}
		);

		return( printableParts.join( " " ) );

	}

	// ---
	// STATIC METHODS.
	// ---

	// NOTE: Since these "factory methods" are part of the Person class definition, they
	// are able to access the PRIVATE CONSTRUCTOR. As such, they can act as a proxy for
	// the instantiation of the Person class.

	static create( first: string, middle: string, last: string ) : Person;
	static create( first: string, last: string ) : Person;
	static create( first: string) : Person;
	static create( a: string, b?: string, c?: string ) : Person {

		switch ( arguments.length ) {
			case 1:
				return( new Person( a, "", "" ) );
				// @ts-ignore: TS7027: Unreachable code detected.
			break;
			case 2:
				return( new Person( a, "", b ) );
				// @ts-ignore: TS7027: Unreachable code detected.
			break;
			default:
				return( new Person( a, b, c ) );
				// @ts-ignore: TS7027: Unreachable code detected.
			break;
		}

	}

	static createFromName( name: string ) : Person {

		var parts = name.split( " " );

		switch ( parts.length ) {
			case 1:
				return( new Person( parts[ 0 ], "", "" ) );
				// @ts-ignore: TS7027: Unreachable code detected.
			case 2:
				return( new Person( parts[ 0 ], "", parts[ 1 ] ) );
				// @ts-ignore: TS7027: Unreachable code detected.
			break;
			case 3:
				return( new Person( parts[ 0 ], parts[ 1 ], parts[ 2 ] ) );
				// @ts-ignore: TS7027: Unreachable code detected.
			break;
			default:
				return( new Person( parts[ 0 ], parts[ 1 ], parts.slice( 2 ).join( " " ) ) );
				// @ts-ignore: TS7027: Unreachable code detected.
			break;
		}

	}

	static createFromParts( parts: NameParts ) : Person {

		return( new Person( parts.first, ( parts.middle || "" ), parts.last ) );

	}

}

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

// Now that we have our class with a private constructor, let's try using the various
// Static Methods to create the class.
var sarah = Person.create( "Sarah", "Danger", "Smith" );

console.log( "USING: .create()" );
console.log( sarah );
console.log( sarah.toString() );
console.log( "--" );

var tricia = Person.createFromName( "Tricia J. Smith Jones" );

console.log( "USING: .createFromName()" );
console.log( tricia );
console.log( tricia.toString() );
console.log( "--" );

var kim = Person.createFromParts({
	first: "Kim",
	middle: "",
	last: "Smith"
});

console.log( "USING: .createFromParts()" );
console.log( kim );
console.log( kim.toString() );
console.log( "--" );

As you can see, the Person class has a private constructor which takes three name components (first, middle, and last). Then, I have several Static methods which provide different ways to invoke the private constructor. And, if we run this code through ts-node, we get the following console output:

Static Methods in TypeScript can access the private constructors of their class.

As you can see, the Static factory methods on the Person class were all able to access and invoke the private class constructor. In essence, this forces all Person class instantiation through the static methods.

To be clear, I am not recommending that you use this as your primary means for class instantiation. This was strictly an exploration of the access rights that static class methods have on the class definition. And, as it turns out, static methods can access private constructors. This may be an obvious thing for classically trained computer programmers. But, for people like me, this is new and exciting information.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Its really an Excellent post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog. Thanks for sharing....

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel