Chris
Chris

Reputation: 442

How should I properly use parent class methods and the keyword extends in typescript?

I have two classes, one a parent, and one a child, that implement types to define their expected functionality. That is below here.

export abstract class BaseLogService implements IBaseLogService
{
    static $inject: string[] = ['$http']
    constructor(private $http:ng.IHttpService){

    }

    baseMethod1 = (objToLog): void => {
        //do stuff
    }

    baseMethod2 = (objToLog): void => {
        //do stuff
    }
}


class LocalLogService extends BaseLogService implements ILocalLogService
{
    constructor(private $http:ng.IHttpService){
        super(this.$http);
    }

    localMethod1 = (): void => {
        //do stuff
    }

}

export interface IBaseLogService {
    baseMethod1(objToLog):void;


    baseMethod2(objToLog):void;
}

export interface ILocalLogService extends IBaseLogService{
    localMethod1(): void;
}

The app utilizes a log service, and logs things for different companies. For company A it might log some different things compared to companies B and C. This is why there is a local log service in each company. But there are universal things that are logged across all companies. So this is why there is a common Log class that the local implementations extend.

With all that introduction out of the way my question is, how do I appropriately utilize my parent class' methods?

To expand on that, let me explain how this is all being used. This is angularjs so in a controller elsewhere in the app there is an api POST call.

On that call's promise return in the .then(), the baseMethod1 needs to be executed to log some common stuff. So it'd be something like the pseudo-code below

myThingToAdd: any;

static $inject: string[] = ['localLogService']

constructor(private localLogService: ILocalLogService){}

this.myPOSTApi.addAThing(this.myThingToAdd)
    .then((POSTResponse) => {
        this.localLogService.baseMethod1(POSTResponse);
    });

This is where my confusion comes into play. I have some company specific stuff that needs to happen when baseMethod1 gets called. But because my service call goes straight to the parent class from the controller, I jump over the local child service where my local logic needs to happen.

I could set it up so my .then() service call goes to my local child class, which does my company specific logic before calling super.baseMethod1(), but that seems very indirect considering I'm able to call the parent class' method straight from my injected service in the controller.

Then I thought, I could make two calls, one for local logic, one for common, each going out to their respective services but that doesn't seem optimized to me. And feels like it defeats the purpose of inheritance.

Then I went down the rabbit hole overthinking my way to where I am now.

Should my parent class methods not be visible to the rest of the app and only be usable inside of my child class? If so how deep should the obfuscation go? Should my child class have publicly exposed methods, that call super.baseMethod1() directly like

localMethod1 = () => {
    //local logic
    super.BaseMethod1();
}

Or is even that too direct? Should I have a publicly exposed child method, that then calls a private internal method that calls my super method? Like

localMethod1 = () => {
    //local logic
    this.callSuperMethod1();
}

private callSuperMethod1 = () => {
    super.baseMethod1();
}

I think I'm severely overthinking the ideas of inheritance, and encapsulation, and would appreciate some advice on how to proceed and strike a balance between pragmatic proper practices and efficient/optimized code.

Upvotes: 1

Views: 124

Answers (1)

Estus Flask
Estus Flask

Reputation: 222795

Inheritance is exactly the way this problem is supposed to be addressed:

I could set it up so my .then() service call goes to my local child class, which does my company specific logic before calling super.baseMethod1(), but that seems very indirect considering I'm able to call the parent class' method straight from my injected service in the controller.

Child class can implement its own baseMethod1 and can also apply the behaviour from a parent with super method call. As explained in this answer, inheritance is one of few reasons why instance arrow methods shouldn't be used. This won't work because instance method doesn't have access to super:

localMethod1 = () => {
    //local logic
    super.BaseMethod1();
}

It is:

export abstract class BaseLogService {
    ...
    constructor(protected $http:ng.IHttpService) {}

    baseMethod1(objToLog): void {
        //do stuff
    }
    ...
}

class LocalLogService extends BaseLogService {
    // can be omitted
    constructor($http:ng.IHttpService){
        super($http);
    }

    baseMethod1(objToLog): void {
        super.baseMethod1(objToLog)
        //do stuff
    }
}

If ILocalLogService completely replicates public BaseLogService interface, it's redundant - BaseLogService can be used as interface. $http should be protected and not private, because it may be used in child classes. Visiblility modifier should be specified only once, in parent constructor. If child constructor does nothing except calling super, it can be omitted.

Since only one logger class is supposed to be used at a time, this is what DI pattern is for. This can be addressed solely with AngularJS DI:

// company A module
app.service('logger', ALogService);

Units that use the service shouldn't be aware of which implementation they are using:

FooController {
  constructor(public logger: BaseLogService) {}
}

Upvotes: 2

Related Questions