Typescript - inheriting static properties without

2019-07-21 09:51发布

问题:

I have multiple classes that represent entities from database.

abstract class Entity {
    static columns: Column[];
    static showInNav: boolean;
    static dependencies: string[];
    // non-static fields
}
class Entity_A extends Entity {
    //static properties redeclaration
    //non-static properties
}
class Entity_B extends Entity {
    //static properties redeclaration
    //non-static properties
}

Every class extends Entity or one of it's children. In initialization phase, I put classes in an array [Entity_A, Entity_B, ...], iterate over them and read their properties to know hot to setup the app. The static properties are basically my configuration.

The problem is, there is no static contract in typescript which makes it easy to make a mistake, and hard to find it (and I've read it's not a good practice in general). I could change static properties to methods and simply do new currentClass().property. But I'm sure there must be a better way.

Any ideas?

Edit (what I actually want): I want to be able to safely define "configuration" in classes (typechecking + mandatory overriding) and easily access it when given array of Classes

回答1:

You could hide the actual Entity class in a module (not export it) and only export a function that has as a parameter the required static fields. This function would return a class derived from the hidden base class and will override the static fields. And the result of this function will be used as the base class for your derived entities:

entity.ts

abstract class EntityImpl {
    static columns: Column[];
    static showInNav: boolean;
    static dependencies: string[];
    abstract doStuff(): void;
}
export interface Column {
    // Dummy for this sample
}

export function Entity(required: { columns: Column[]; showInNav: boolean; dependencies: string[];}) {
    abstract class Entity  extends EntityImpl {
        static columns: Column[] = required.columns
        static showInNav: boolean = required.showInNav;
        static dependencies: string[] = required.dependencies;
    }
    return Entity;
}
// Export a type definition so we can use the base type as needed 
export type Entity = EntityImpl;
// Export a type definition that represents the type, so we can access all the static props 
export type EntityClass = typeof EntityImpl;

impl.ts

import { Entity, EntityClass } from './entity'

class Entity_A extends Entity({
    columns: [],
    dependencies: [],
    showInNav: true
}) {
    doStuff(): void {} //abstract methids HAVE to be imlementes as expected
}

class Entity_B extends Entity({
    columns: [],
    dependencies: [],
    showInNav: false
}) {
    doStuff(): void {}
}

// Array of types, with save access
var allClasses : Array<EntityClass> = [Entity_A, Entity_B];
for(let type of allClasses) {
    console.log(type.showInNav);
}

// Array of instances, with save access
var data: Array<Entity> = [new Entity_A(), new Entity_B()];
data.forEach(x => x.doStuff());

This approach keeps the fields static, and it forces implementers to specify them. Also as far as I can tell features such as forcing you to implement abstract methods work.

If you need a class that derives Entity but is a base class for other classes you can apply the same pattern, that is incapsulate it in a function and use the function as the base class.