Encapsulation and Abstraction in Typescript

javascript Aug 05, 2019

Encapsulation

The concept of encapsulation in OOP allows us to define all of the necessary members for an object to function while hiding the internal implementation details from the application using the object. This is a very powerful concept for us to use because it allows us to change the internal workings of a class without breaking any of its dependent objects. This could come in the form of either private members or private method implementations. The contract that has been established remains the same and the functionality that it provides is consistent, however, improvements are able to be made. In the following code segment, you can see how hiding certain members from the calling application will be beneficial:

interface IActivator {
    activateLicense(): boolean;
}
class LocalActivator implements IActivator {
    private _remainingLicenses: number = 5;
    constructor() {
     }
    public activateLicense(): boolean {
        this._remainingLicenses--;
        if (this._remainingLicenses > 0)
            return true;
        throw "Out of Licenses";
    }
}

In this example, we have an interface that defines a type that will manage the activation of licenses for a product. This is followed by a concrete implementation of the interface that locally keeps track of the remaining licenses and returns a true value if there are remaining licenses, otherwise an exception is thrown to stop program execution. The number of licenses is kept in a private variable so that the calling application can't give itself more licenses to work with. In real-world scenarios, however, the actual implementation details would likely make a call to a server that determines whether the product has licenses remaining. Thanks to encapsulation, we could change the implementation details of this class too without the client ever knowing the difference, other than the delay in making a server request. In the following code segment, you can see how we are able to swap the LocalActivator object with a ServerActivator object and program execution continues to function as expected:

class ServerActivator implements IActivator {
    constructor() {
    }
    public activateLicense(): boolean {
        var request = new XMLHttpRequest();
        var requestResponse: boolean = false;
        request.open('GET', '/license/activate', false);

        request.onload = () => {
            if (request.status == 200) {
                requestResponse = true;
            } else {
                throw "An error occured during activation!";
            }
        };
         request.onerror = () => {
            throw "An error occured during activation!";
        }
        request.send(null);
        return requestResponse;
    }
}
//var activator: IActivator = new LocalActivator();
var activator: IActivator = new ServerActivator();
var isActive = activator.activateLicense();
if (isActive) {
    //Program logic
}

The ServerActivator class implements the same interface as LocalActivator we declared earlier, but it makes a synchronous request back to the server for data rather than doing it on the client. Normally, this type of request should be made asynchronously, however, we would need to change the IActivator interface to support promises or use callbacks.

Note

Promises will be natively supported in ECMAScript 6, however several libraries have implemented their own implementation.

The implementation details between the two objects is vastly different but the end result is the same. The two can be used interchangeably and the application requires no further changes.

Abstraction

Abstraction is an incredibly powerful concept in object-oriented development. It encompasses the idea of hiding specific implementation details while providing the high-level definition of what should be implemented. In the previous example, we saw a very basic case of abstraction. The IActivator interface creates the abstraction layer needed to handle the concept of activating the application. The LocalActivator and ServerActivator types are concrete implementations of this abstraction. In other programming languages such as C#, classes are able to declare specific members as abstract. This forces any subclasses of the base type to provide a concrete implementation of that member. In the following code segment, you will see a C# example of this:

public abstract class AbstractBaseClass
{
    protected bool isActive = false;
    public AbstractBaseClass()
    {
    }
    public abstract bool CheckStatus();
}
public class ConcreteClass : AbstractBaseClass
{
    public ConcreteClass()
    {
    }
    public override bool CheckStatus()
    {
        return this.isActive;
    }
}

The base class provides the declaration for a method called CheckStatus, however, no implementation is provided. The implementation is forced upon the ConcreteClass type, however, members made available in the base class are available to this implementation. The abstract keyword on the CheckStatus method tells the compiler that all subclasses of the AbstractBaseClass must implement this method or a compile error will be generated. The abstract keyword on the

AbstractBaseClass will cause the compiler to generate an error if the class is instantiated directly. The simplest way to do this in TypeScript is through the use of interfaces, however, this does not provide the full functionality that comes from abstract classes in C#. The protected member in the base class is not accessible outside of the concrete implementations of the type, however if it is placed on a TypeScript interface, then it must be made publicly available. Despite the lack of an abstract keyword and language construct in TypeScript, we can use certain patterns to create a similar implementation:

class AbstractClass {
    constructor() {
    }
    public checkStatus(): boolean {
        throw new Error("Not Implemented");";
    }
}
class ConcreteClass extends AbstractClass {
    private _isActive: boolean = false;
    constructor() {
        super();
    }
    public checkStatus(): boolean {
        return this._isActive;
    }
}

As you can see, we must still define a complete class that represents the abstract base class. However, in the method implementation of checkStatus, an exception is thrown at runtime that will stop program execution whenever an attempt to use this class directly is made. It would be nice if there was some way to enforce this at compile time, however, currently this functionality does not exist. TypeScript does not have a protected accessibility level so we have been forced to move this detail down into the concrete class explicitly. In this specific case, it would make sense to just make the AbstractClass into an interface that the ConcreteClass type would then implement; however, as our objects become more complex, we will want to have publicly accessible members that are defined only once in the base type and specific method implementations will need to be provided by the concrete classes.

Neeraj Dana

Experienced Software Engineer with a demonstrated history of working in the information technology and services industry. Skilled in Angular, React, React-Native, Vue js, Machine Learning