我正在使用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));
}
}
要尝试重申我要做的事情,我想改为将fromJson
和toJson
设为静态方法。在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。
答案 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耦合,因此它不是一个很好的基类(您不能与其他类共享它)。