Reputation: 468
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
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