lebed2045
lebed2045

Reputation: 468

How to declare interface with static method and constructor signature in TypeScript?

I have two classes with common static method. What's the best way to add common interface which includes static method implementation and constructor signature?

I wrote BitcoinHDWallet and EthereumHDWallet classes which implement wallet logic for corresponded blockchains. I would like to add common interface HDWallet which going to describe logic to work with both of them so user can do:

    const multiWallet: Array<HDWallet> = [];
    multiWallet[0] = new BitcoinHDWallet(...);
    multiWallet[1] = new EthereumHDWallet(...);

These two classes share some common static methods.

0) If I use abstract class as the way to describe interface, I don't know how to add constructor signature.

1) If I use interface, I can't add static method implementation.

Ideally, I want to do something like that:

abstract class HDWallet {
  /**
   * Return new random mnemonic seed phrase
   */
  static generateMnemonic(): string {
    return Bip39.generateMnemonic();
  }

  constructor(bip39SeedPhrase: string, password?: string, testnet?: boolean);

  abstract getAddress(addressIndex?: number) : string;

  abstract async getBalance(address? : string) : Promise<number>; 

  ...
}


class BitcoinHDWallet extends HDWallet {...}
class EthereumHDWallet extends HDWallet {...}

Upvotes: 1

Views: 4166

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249506

There is no to do this out of the box. The abstract class is the closest thing to what you want, but it will indeed not allow you to check that the type has a specific constructor.

One way to do this is to add an extra type parameter to the class. This can be constrained to typeof HDWallet which will represent the constructor signature of the base class. Derived classes must pass themselves as this extra parameter and then their constructor signature will be checked to be compatible with the base class signature:

abstract class HDWallet<T extends typeof HDWallet> {
  /**
   * Return BIP39 12 words new random mnemonic seed phrase
   */
  static generateMnemonic(): string {
    return "";
  }

  constructor(bip39SeedPhrase: string, password?: string, testnet?: boolean) { }

  abstract getAddress(addressIndex?: number) : string;

  abstract async getBalance(address? : string) : Promise<number>; 


}


class BitcoinHDWallet extends HDWallet<typeof BitcoinHDWallet> {
  getAddress(addressIndex?: number) : string { return ""}

  async getBalance(address?: string): Promise<number> { return Promise.resolve(0);} 
}
class EthereumHDWallet extends HDWallet<typeof EthereumHDWallet> { /// error

  constructor(testnet?: boolean) { 
    super("", "", false)
  }
  getAddress(addressIndex?: number) : string { return ""}

  async getBalance(address?: string): Promise<number> { return Promise.resolve(0);} 
}

Note using interfaces, you can describe the static part of the class as well as the instance type, depends what you are trying to validate:

interface HDWalletClass {
  generateMnemonic(): string 

  new (bip39SeedPhrase: string, password?: string, testnet?: boolean): {

    getAddress(addressIndex?: number): string;

    getBalance(address?: string): Promise<number>;
  }
}
abstract class HDWallet {
  /**
   * Return BIP39 12 words new random mnemonic seed phrase
   */
  static generateMnemonic(): string {
    return "";
  }

  constructor(bip39SeedPhrase: string, password?: string, testnet?: boolean) { }

  abstract getAddress(addressIndex?: number) : string;

  abstract async getBalance(address? : string) : Promise<number>; 


}


class BitcoinHDWallet extends HDWallet {
  getAddress(addressIndex?: number) : string { return ""}

  async getBalance(address?: string): Promise<number> { return Promise.resolve(0);} 
}
class EthereumHDWallet extends HDWallet { /// error

  constructor(testnet?: boolean) { 
    super("", "", false)
  }
  getAddress(addressIndex?: number) : string { return ""}

  async getBalance(address?: string): Promise<number> { return Promise.resolve(0);} 
}
let a: HDWalletClass = BitcoinHDWallet; //ok
let b: HDWalletClass = EthereumHDWallet; // err

Upvotes: 1

Related Questions