首页 > 解决方案 > 在变量打字稿上动态调用静态方法 - 理论/实现

问题描述

我正在使用 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()这是我试图在这里找到的等价物,但似乎不可能。

我发现这个问题(动态调用静态方法)似乎触及了我想做的事情,但似乎必须有更好的方法。在我的每个类中都这样做,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();

我想要一些方法来在每个休息服务上存储序列化程序类的名称,并让它从该序列化程序类调用静态函数。或者,如果有更好的方法来实现我正在寻找的东西,我愿意接受想法。

标签: typescript

解决方案


简短的回答是,你不能——你不能在变量上调用静态方法,因为在 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更符合当前趋势)并在那里定义这两个方法。现在你有一种在每个对象中调用 then 的简单方法。您可以创建一个基类 ( abstract class Jsonable),该基类具有可供这些类使用的基本逻辑。抽象类应该包含通用逻辑。

同时拥有接口和抽象基类的唯一原因是避免强制对象继承基类。所以调用站点应该期待这个接口,但是你可以选择使用基本行为的类从某个地方继承。

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

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


推荐阅读