Я не большой поклонник наследования, чтобы обеспечить расширяемость (в смысле принципа Open / Closed ) .Тем не менее, в этой ситуации я бы предпочел спроектировать базовый класс так, чтобы он был явно расширяемым и для чего, здесь, в его входном параметре конфигурации, используя ограничение общего типа и экспортируя базовый тип конфигурации.
// base.ts ---------------------------------------------------------------------
export interface IBaseConfig {
someProperty: string;
}
export class Base<TConfig extends IBaseConfig = IBaseConfig> {
constructor(config: TConfig) {
}
}
// derived.ts ------------------------------------------------------------------
interface IDerivedConfig extends IBaseConfig {
otherProperty: string;
}
export class Derived extends Base<IDerivedConfig> {
constructor(config: IDerivedConfig) {
super(config);
}
}
Редактировать
Когда «базовый» класс не в нашей руке, требуется дополнительная осторожность:
- Если «базовый» класс не являетсяпредназначен для расширения наследованием, не наследуйте от этого класса, используйте композицию с forwarding .Таким образом, мы защищаем себя от трудно обнаруживаемых ошибок, таких как Хрупкий базовый класс .Больше информации: см. Состав по наследованию .
- Защитите клиента нашего класса от изменений в "базовом" классе.Освойте все открытые типы и скройте другие типы, например, обернув тип из
base.ts
.
Это один из вариантов:
// base.ts ---------------------------------------------------------------------
interface IConfig {
someProperty: string;
}
export class Base {
constructor(config: IConfig) { }
compute(arg: any): string { return 'any-string'; }
do(): void {}
}
// derived.ts ------------------------------------------------------------------
export interface IDerivedConfig { /* TBD */ }
export class Derived {
static create(config: IDerivedConfig): Derived {
const baseConfig = {} as any /* TODO: replace `{} as any` with the custom mapping from `config` to a base config object. */;
const base = new Base(baseConfig);
return new Derived(base);
}
private constructor(
private readonly base: Base,
) { }
compute(arg: any) {
return this.base.compute(arg);
}
do() {
this.base.do();
}
}
Примечания:
IDerivedConfig
не расширяет "IConfig
" (который мы получаем с вашим наконечником: type BaseConfigType = ConstructorParameters<typeof Base>[0]
) , чтобы быть полностью в наших руках и предотвратить изменение вбазовый конфиг для распространения на все строительные площадки наших клиентов. Derived
класс implements Base
(вместо extends Base
).Тем не менее, нет необходимости кодировать его, но сначала нужно быть уверенным в совместимости типов.Тогда удаление будет безопаснее.Если базовый класс изменяется, мы можем распространить это изменение или просто закодировать наш класс как Adapter . Derived
класс имеет статический метод фабрики для его создания, функцию, которая вызываетзакрытый конструктор, который определяет встроенный частный доступный только для чтения «базовый» объект. - В методе
Derived.create()
baseConfig
вводится неявно с помощью логического вывода.Код безопасен, потому что компилятор не позволяет нам передавать несовместимые аргументы в конструктор Base
.