如何扩展依赖的TypeScript接口



我有一个主要的TypeScript项目作为一个包发布,可以通过插件(只是其他的TypeScript包)来丰富。

我的主要项目公开了一个API,其定义如下

class Dealer {
public buyCar (model: string, options: any): void {
// ...
}
}

假设主项目允许从经销商那里购买汽车,并且插件可以添加可供购买的新车型。如何从插件包扩展buyCar定义以添加更具体的类型?

之类的
class Dealer {
public buyCar (model: "Toyota", options: ToyotaOptions): void;
}

我知道我可以从主项目中做到这一点,但关键是主项目不应该知道插件,所以插件必须扩展接口。

您可以尝试使用泛型来指定类期望接收的模型和选项的类型。

interface Dealer<M extends string> {
buyCar<T = unknown>(model: M, options: T): void;
}

type ToyotaModel = "Yaris" | "Auris" | "Camry";
interface ToyotaOptions {
engine?: "petrol" | "diesel" | "hybrid";
}
class ToyotaDealer implements Dealer<ToyotaModel> {
public buyCar<ToyotaOptions>(model: ToyotaModel, options: ToyotaOptions) {
console.log(model, options);
}
}
const toyota = new ToyotaDealer();
toyota.buyCar("Auris", { engine: "petrol" });

整个类被M泛型注释,以使类中的所有方法都访问该类型并使用相同的模型集。buyCar方法由另一个通用的T注释,以允许指定"购买汽车"的接口。

TypeScript playground的示例在这里。

我不是用户,但我认为你可以组合命名空间&声明合并来实现它。像这样:

type ToyotaOptions = "Toyota";
namespace Dealer {
export function buyCar(model: string, options: ToyotaOptions): void;
export function buyCar(model: string, options: any): void {
Dealer.buyCar(model, options);
}
}

我不确定我是否正确理解了您,但我认为您对插件架构的描述存在一些误解。你不应该扩展/修改/覆盖插件的代码来使用它(例如,通过使用命名空间&声明合并,因为它违反了开闭原则)。为了使插件对修改关闭,我将引入一个所有插件包都将使用的共享接口。例如

// package: purchases
export interface CarPurchase {
buy():void
}

提供购买丰田汽车的详细信息的包可能是这样的。

// package: toyota
import { CarPurchase } from "purchases"
export type ToyotaYarisPurchaseOptions {
engine?: "petrol" | "diesel" | "hybrid";
}
export class ToyotaYarisPurchase implements CarPurchase {
constructor(options: ToyotaYarisPurchaseOptions){}
buy():void {
//implementations details goes here
}
}
export type ToyotaCamryPurchaseOptions {
engine?: "petrol" | "diesel" | "hybrid";
}
export class ToyotaCamryPurchase implements CarPurchase {
constructor(options: ToyotaCamryPurchaseOptions){}
buy():void {
//implementations details goes here
}
}

然后,在你的主应用程序中,我可以实现运行购买的Dealer类。

import { CarPurchase } from "purchases"
import { ToyotaYarisPurchase } from "toyota"
class Dealer {
makeTransaction(purchase: Purchase):void {
// additional behavior for a dealer - e.g. logging all purchases
purchase.buy();
}
}
const dealer = new Dealer();
const toyotaPurchase = new ToyotaYarisPurchase({engine: 'diesel'})
dealer.makeTransaction(toyotaPurchase);

此时,您还可以为购买创建一个工厂,以隐藏购买的实例化细节:


// purchasesFactory.ts
import { ToyotaYarisPurchaseOptions } from "toyota"
import { CarPurchase } from "purchases"

export class PurchasesFactory {
create(car: 'yaris', options: ToyotaYarisPurchaseOptions): ToyotaYarisPurchase
create(car: 'camry', options: ToyotaCamryPurchaseOptions):ToyotaCamryPurchase
create(car: 'yaris' | 'camry', options: ToyotaYarisPurchaseOptions | ToyotaCamryPurchaseOptions): CarPurchase {
switch(car) {
case 'camry':
return new ToyotaCamryPurchase(options);
// other cases
}
}   
}

所以你最终的应用代码看起来像

import { CarPurchase } from "purchases"
import { ToyotaYarisPurchase } from "toyota"
import { PurchasesFactory } from './purchasesFactory';
class Dealer {
makeTransaction(purchase: Purchase):void {
// additional behavior for a dealer - e.g. logging all purchases
purchase.buy();
}
}
const dealer = new Dealer();
const purchases = new PurchasesFactory();
const toyotaYarisPurchase = purchases.create('yaris', { engine: 'diesel' });
dealer.makeTransaction(toyotaYarisPurchase);

给出的示例大致说明了命令模式的用法。它可能适合你的问题。我使用OOP的目的是为了清楚地表示特定类的职责,但是您可以使用泛型类型的函数更简洁地编写它。(注意,向购买函数和经销商的makeTransaction注入额外的依赖关系需要使用更高级的概念)。


// shared
export type Purchase<TOptions extends object> = <TOptions>(options:TOptions) => void;
// toyota package 
export const buyToyotaYaris: Purchase<ToyotaYarisPurchaseOptions> = options => {
// some logic here
}
export const buyToyotaCamry: Purchase<ToyotaCamryPurchaseOptions> = options => {
// some logic here
}

// main app
import { buyToyotaCamry } from 'toyota';
const makeTransaction = <TPurchaseOptions extends object>(options: TPurchaseOptions, purchase: Purchase<T>) => {
// dealer logic here
purchase(options)
}
makeTransaction({engine: 'diesel'}, buyToyotaCamry);

我认为您是在尝试用语言特定结构来解决软件设计问题。你可以用一种更通用的、适用于任何面向对象语言的方法来解决这个问题,然后把它翻译成TypeScript。

如果你把这个贴在软件工程网站上,也许你会得到一个更好的答案。

在任何情况下,我都会这样做:

  1. 通过基本包中的接口定义非常基本的概念(经销商工作所需的)。
    • 汽车模型通用方面
    • 汽车选项通用性
    • 汽车经销商。让汽车经销商使用这个非常基本的概念,你已经有了
  2. 定义你的插件架构。也许这有帮助
    • 插件注册机制,通过其选项注册新车型
    • 允许你的主应用程序请求汽车模型到你的汽车(插件)寄存器
    • 让您的汽车经销商从注册
    • 中使用这些新添加的(更具体的)车型

这样,比方说,你可以安装一个带有新汽车定义的npm包,然后在你的应用程序中将这个新汽车定义注册到你的汽车注册表中,然后经销商可以稍后使用它。

您可能希望这样做以获得更好的可读性和可维护性。

import { Dealer as BaseDealer } from './Dealer';
export class Dealer extends BaseDealer {
public buyCar(model: string, options: any): void {
super.buyCar(model, options);
}
}

演示:https://stackblitz.com/edit/typescript-tpdfyv?file=index.ts

最新更新