在变量打字稿上动态调用静态方法-理论/实现

时间:2019-03-11 17:51:23

标签: typescript

我正在使用Ionic 4和Angular 7创建一个混合移动应用程序。该应用程序将与我用PHP(Yii2)开发的API通信,以向用户显示数据。

我想创建一个通用的REST API类来与服务器通信,并且我已将本文用作开发基准(https://medium.com/@krishna.acondy/a-generic-http-service-approach-for-angular-applications-a7bd8ff6a068)。

根据我的理解,阅读其他多篇文章和文档,该文章中的序列化程序类示例应将方法fromJson()toJson()声明为静态方法。我习惯在PHP中做这种事情。由于此对象没有状态,而这些本质上是辅助函数,因此,当我真正仅使用这些辅助函数时,我不希望这些类有多个实例(如果我在这里出错,请帮助我理解)。

但是,在rest.service.ts文件中,这些函数是在实例(this.serializer.fromJson(data))上调用的。要从阅读的内容中调用静态方法,我需要像ClassName.staticMethod()那样调用它,但是在rest.service.ts的上下文中,我无法做到这一点,因为我不知道如何动态地调用静态方法。变量(this.serializer.staticMethod())。在PHP中,我习惯于能够调用$className::staticMethod(),这是我试图在此处找到等效项的方法,但似乎不可能。

我发现了这个问题(Dynamically calling a static method),这似乎与我想做的事情有关,但似乎必须有一个更好的方法。这样,在我的每个XYZ.service.ts类中,我都必须使用序列化程序类的静态方法写出相同的映射,这似乎在重复我自己,并使我认为必须针对此问题进行更好的实践我正在努力实现。

所有这些的另一个原因是,在我的序列化程序中,有时我需要引用另一个序列化程序作为子资源。现在,我正在创建一个实例来执行此操作,但是这似乎效率不高,并且进一步使我认为应该静态地执行此操作,因为我可能创建了多个XYZSerializer实例才能运行我觉得应该是静态的方法。

这是我现在以上面的文章为模型的工作代码示例,为便于阅读而缩写:

export interface Serializer {
    fromJson(json: any): ApiResource;
    toJson(resource: ApiResource): any;
}

export class SharkBiteSerializer implements Serializer{
    playerSerializer: PlayerSerializer = new PlayerSerializer;

    fromJson(json: any): SharkBite {
        const sharkBite = new SharkBite();
        sharkBite.id = json.id;
        ...
        sharkBite.featuredPlayer = this.playerSerializer.fromJson(json.featuredPlayer);
        return sharkBite;
    }

    toJson(sharkBite: SharkBite): any {
        return {
            id: sharkBite.id,
            ...
        };
    }
}

export class SharkBiteService extends RestService<SharkBite> {
    endpoint = 'shark-bites'
    serializer = new SharkBiteSerializer;

    constructor(
        httpClient: HttpClient,
    ) 
    {
        super(httpClient);
    }
}

export abstract class RestService<T extends ApiResource> {
    abstract endpoint: string;
    abstract serializer: Serializer;

    constructor(
        private httpClient: HttpClient,
    ) {}

    /**
     * Add a new record
     */
    public create(item: T): Observable<T> {
        return this.httpClient
            .post<T>(`${environment.apiUrl}/${this.endpoint}`, this.serializer.toJson(item))
            .pipe(
                map(data => this.serializer.fromJson(data) as T)
            );
    }

    /**
     * Update an existing record
     */
    public update(item: T): Observable<T> {
        return this.httpClient
            .put<T>(`${environment.apiUrl}/${this.endpoint}/${item.id}`, this.serializer.toJson(item))
            .pipe(
                map(data => this.serializer.fromJson(data) as T)
            );
    }

    /**
     * Retrieve a single record
     */
    public read(id: number): Observable<T> {
        return this.httpClient
            .get(`${environment.apiUrl}/${this.endpoint}/${id}`)
            .pipe(
                map(data => this.serializer.fromJson(data) as T)
            );
    }

    /**
     * Retrieves muleiple records based on a query
     */
    public list(httpParams: HttpParams): Observable<T[]> {
        return this.httpClient
            .get(`${environment.apiUrl}/${this.endpoint}`, {params: httpParams})
            .pipe(
                map((data: any) => this.convertData(data))
            );
    }

    /**
     * Delete a single record
     */ 
    public delete(id: number) {
        return this.httpClient
            .delete(`${environment.apiUrl}/${this.endpoint}/${id}`);
    }

    /**
     * Converts the array of items into an array of the typed items
     */
    private convertData(data: any): T[] {
        return data.map(item => this.serializer.fromJson(item));
    }
}

要尝试重申我要做的事情,我想改为将fromJsontoJson设为静态方法。在rest.service.ts中,我应该呼叫SerializerClassName.toJson()SerializerClassName.fromJson();然后可以在SharkBiteSerializer中静态调用PlayerSerializer.toJson()PlayerSerializer.fromJson()。我开玩笑地使用抽象类Serializer将方法设为静态,但是我似乎无法弄清楚如何在变量上调用静态方法。

如果我在这里没有遵循最佳做法,请赐教。我是Typescript的新手,我意识到我的PHP背景可能正在渗透。我只是在尝试找到实现此目标的最佳方法,并愿意提出任何建议。谢谢!

更新:

我了解无法在类的实例上调用静态方法的概念。我想知道是否可以以任何方式将类名存储在变量中,然后对存储在该变量中的类名调用静态方法。例如,在PHP中,我可以:

$className = '\namespace\for\ClassName';
echo $className::staticMethod();

我想以某种方式在每个rest服务上存储序列化器类的名称,并让它从该序列化器类中调用静态函数。或者,如果有一种更好的方法来实现我所寻找的内容,那么我会敞开心ideas。

1 个答案:

答案 0 :(得分:1)

简短的答案是,您不能-您不能在变量上调用静态方法,因为在TypeScript上,静态方法属于类,而不是变量。

也就是说,您可以修改它,因为TypeScript变成了JavaScript。这不是很干净,因为您将失去使用TypeScript所获得的适当的键入,但是至少它使您能够放心使用。

下面的TS代码...

class Foo {
    public static bar() {
        console.log("bar");
    }
}

Foo.bar();

...编译为

var Foo = /** @class */ (function () {
    function Foo() {
    }
    Foo.bar = function () {
        console.log("bar");
    };
    return Foo;
}());
Foo.bar();

这意味着bar不在原型上(因此为什么它不会被继承),而是留在类本身上。

访问类函数的方法是通过构造函数属性,因此:

class Foo {
    public static bar() {
        console.log("bar");
    }
}

function testIt(f: Object) {
    (f.constructor as any).bar();
}

let f = new Foo();
testIt(f);

再次,不干净,但这是一种方法。


现在,这里最大的问题是:为什么您真的需要静态方法?我想这有一定的习惯(上帝知道我会写很多静态的东西,直到我记得我不应该写),但是通常这是没有必要甚至不建议的。静态方法是过程编程的泄漏,在面向对象的代码中几乎没有位置。您会从中获得很少的收益(可以肯定的是分配较少,但这甚至不适用于您的情况),而从通用方法中可以获得很多好处。

对于您而言,如您在本文中所见,您应该像您所说的那样定义一个Serializable接口(我将其命名为Jsonable是为了与当前趋势保持一致)并在那里定义这两种方法。现在,您可以在每个对象中进行简单的调用。然后,您可以创建一个基类(abstract class Jsonable),该基类具有可以被这些类使用的基逻辑。抽象类应包含通用逻辑。

同时具有接口和抽象基类的唯一原因是避免强制对象继承基类。因此,呼叫站点应该使用该接口,但是您可以选择使用基本行为的类从某个地方继承。

(顺便说一句,我什至不在这里做一个抽象,只是一个带有虚拟方法的可继承类,可以由继承和合成使用)

当然,仅当您可以实际编写执行所需序列化的通用代码时,基本抽象类才有意义。在您提供的示例中,SharkBiteSerializer与SharkBite耦合,因此它不是一个很好的基类(您不能与其他类共享它)。