You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让HttpClient.get返回抽象类的实际子类实例并存入缓存?

问题描述

我定义了抽象类DataEntry:

export abstract class DataEntry {
  ...
  public abstract clone(): DataEntry;
}

以及继承它的实际子类Record:

export class Record extends DataEntry {
  ...
  public clone(): Record {
    return new Record(...);
  }
}

通过DbFacadeService的HttpClient.get从数据库请求数据:

export class DbFacadeService {
  public static BASEURL: string = 'http://localhost:3000';
  constructor(private _http: HttpClient) { }
  public readAll(): Observable<DataEntry[]> {
    return this._http.get<DataEntry[]>(`${DbFacadeService.BASEURL}`, { observe: 'response' })
                     .pipe(map((response: HttpResponse<DataEntry[]>) => response.body || []),
                           first());
  }
  ...
}

再通过DataHandlerService定期调用该方法存入缓存:

export class DataHandlerService {
  ...
  constructor(private _cache: Map<string, DataEntry>,
              private _dbFacade: DbFacadeService) {
    this._cache = new Map<string, DataEntry>([]);
    timer(0, 60000).subscribe(() => this.updateCache());
  }
  private updateCache(): void {
    this._dbFacade.readAll()
                  .subscribe((entries: DataEntry[]) => {
                               entries.forEach((entry: DataEntry) => this._cache.set(entry.id, entry));
                               ...
                             });
  }

  public readEntry(id: string): DataEntry | undefined {
    return this._cache.get(id);
  }
}

在EditorComponent调用缓存数据时,调用clone方法触发运行时异常——缓存里的对象只是HTTP返回的普通对象,不是真正的DataEntry或Record实例,没有clone方法。

尝试过类型转换,但无法创建实际的DataEntry对象:

.pipe(map((response: HttpResponse<DataEntry[]>) =>
            response.body?.map((entry: any) => entry as DataEntry) || [])

已知需要手动转换,但无法实例化抽象类DataEntry,且DataHandlerService不知道具体子类类型。希望缓存中存储实际的Record等子类实例而非any类型,求可行方案,是否只能每次取缓存时再创建实例?

可行解决方案

方案1:给子类添加静态工厂方法(推荐)

在每个DataEntry子类中添加静态方法实现从普通对象到子类实例的转换,同时让抽象类定义这个规范:

首先修改抽象类:

export abstract class DataEntry {
  public abstract id: string;
  public abstract clone(): DataEntry;

  // 定义工厂方法规范,子类必须实现
  public static fromJSON<T extends DataEntry>(json: any): T {
    throw new Error('子类必须实现fromJSON方法');
  }
}

然后在Record子类实现该方法:

export class Record extends DataEntry {
  public id: string;
  // 其他业务属性...

  constructor(id: string, /* 其他初始化参数 */) {
    super();
    this.id = id;
    // 初始化其他属性
  }

  public clone(): Record {
    return new Record(this.id, /* 复制其他属性 */);
  }

  // 实现工厂方法,映射JSON属性到类实例
  public static fromJSON(json: any): Record {
    return new Record(
      json.id,
      json.title, // 示例属性,根据实际业务补充
      json.content
    );
  }
}

接下来需要后端返回的数据中携带类型标识(比如type: "record"),然后在DbFacadeService中维护类型到子类的映射并处理转换:

export class DbFacadeService {
  // 维护类型与子类的映射关系,新增子类时在这里补充
  private typeToClassMap: Record<string, typeof DataEntry> = {
    'record': Record
  };

  public static BASEURL: string = 'http://localhost:3000';
  constructor(private _http: HttpClient) { }

  public readAll(): Observable<DataEntry[]> {
    return this._http.get<any[]>(`${DbFacadeService.BASEURL}`, { observe: 'response' })
                     .pipe(
                       map(response => {
                         const rawEntries = response.body || [];
                         return rawEntries.map(entry => {
                           const clazz = this.typeToClassMap[entry.type];
                           if (!clazz) {
                             throw new Error(`未知数据类型: ${entry.type}`);
                           }
                           return clazz.fromJSON(entry);
                         });
                       }),
                       first()
                     );
  }
}

处理后readAll返回的就是真正的子类实例,存入缓存后调用clone方法不会再触发异常。

方案2:使用类转换器库

借助class-transformer库自动完成普通对象到类实例的转换,无需手动编写工厂方法:

首先安装依赖:

npm install class-transformer

在子类属性上添加装饰器标记需要映射的字段:

import { Expose } from 'class-transformer';

export class Record extends DataEntry {
  @Expose()
  public id: string;
  @Expose()
  public title: string;
  // 其他业务属性...

  public clone(): Record {
    return new Record(this.id, this.title);
  }
}

然后修改DbFacadeService的转换逻辑:

import { plainToInstance } from 'class-transformer';

export class DbFacadeService {
  private typeToClassMap: Record<string, typeof DataEntry> = {
    'record': Record
  };

  public static BASEURL: string = 'http://localhost:3000';
  constructor(private _http: HttpClient) { }

  public readAll(): Observable<DataEntry[]> {
    return this._http.get<any[]>(`${DbFacadeService.BASEURL}`, { observe: 'response' })
                     .pipe(
                       map(response => {
                         const rawEntries = response.body || [];
                         return rawEntries.map(entry => {
                           const clazz = this.typeToClassMap[entry.type];
                           if (!clazz) {
                             throw new Error(`未知数据类型: ${entry.type}`);
                           }
                           return plainToInstance(clazz, entry);
                         });
                       }),
                       first()
                     );
  }
}

方案3:在读取缓存时转换(备选)

如果不想在请求阶段处理转换,也可以在DataHandlerService读取缓存时完成实例化,但每次读取都会创建新实例,适合读取频率较低的场景:

export class DataHandlerService {
  // 缓存存储原始JSON对象
  private _cache: Map<string, any> = new Map<string, any>([]);
  private typeToClassMap: Record<string, typeof DataEntry> = {
    'record': Record
  };

  constructor(private _dbFacade: DbFacadeService) {
    timer(0, 60000).subscribe(() => this.updateCache());
  }

  private updateCache(): void {
    this._dbFacade.readAll()
                  .subscribe((rawEntries: any[]) => {
                               rawEntries.forEach(entry => this._cache.set(entry.id, entry));
                             });
  }

  public readEntry(id: string): DataEntry | undefined {
    const rawEntry = this._cache.get(id);
    if (!rawEntry) return undefined;

    const clazz = this.typeToClassMap[rawEntry.type];
    if (!clazz) throw new Error(`未知数据类型: ${rawEntry.type}`);
    return clazz.fromJSON(rawEntry);
  }
}

内容的提问来源于stack exchange,提问作者Otto Abnormalverbraucher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 23:04:58