Итак, давайте поговорим о #typescript, его суперспособностях и о том, как его можно использовать для генерации новых классов на лету и инстанцирования классов, которых на самом деле не существует. Мы будем создавать летающих собак и гордых уток.
Пожалуй, одна из самых увлекательных особенностей #typescript — это его миксины, которые позволяют лепить классы (и не только их). В этой статье мы узнаем, как можно использовать mixin базового класса для создания четырех различных производных классов. Это было довольно забавное открытие.
Изображение обложки для этой статьи не случайно, в какой-то степени оно вдохновило статью. Итак, давайте подумаем, как можно использовать #typescript для создания экземпляров собаки, рыбы, курицы и утки. Если подумать, то у них явно должен быть какой-то суперкласс. Они также реализуют различные способы передвижения. Для простоты предположим, что некоторые функции реализованы одинаково для всех классов, т.е. ходьба одинакова для собаки и курицы (хотя в реальности куры не ходят на четырех ногах).
Теперь мы можем предположить суперкласс, который будет нашей базой:
class Animal {
constructor(public readonly name: string) { }
}
Простое и понятное животное, у которого есть имя.
Наши животные реализуют различные способы передвижения, которые можно описать с помощью интерфейсов #typescript:
interface IFly {
fly(): void;
}
interface IWalk {
walk(): void;
}
interface ISwim {
swim(): void;
}
Так, собака реализует IWalk
, а курица реализует и IFly
, и IWalk
.
Обычно мы бы начали создавать все эти классы, но миксины предоставляют более интересный и гибкий способ: вместо статического расширения суперкласса мы можем сделать это динамически с помощью миксинов (которые по сути являются декораторами класса, используемыми более простым способом).
Прежде чем создавать сами миксины, мы можем создать конструктор generic, который будет использоваться с миксинами.
interface IConstructor<T> { new(...args: any[]): T; }
Когда конструктор generic готов, следующим шагом будет создание трех миксинов, которые обогатят переданный класс Animal
новыми функциональными возможностями:
function Walker<T extends IConstructor<Animal>>(animal: T) {
return class extends animal implements IWalk {
walk(): void {
console.log(`I am a ${this.name} and I can walk!`);
}
}
}
function Swimmer<T extends IConstructor<Animal>>(animal: T) {
return class extends animal implements ISwim {
swim(): void {
console.log(`I am a ${this.name} and I can swim!`);
}
}
}
function Flyer<T extends IConstructor<Animal>>(animal: T) {
return class extends animal implements IFly {
fly(): void {
console.log(`I am a ${this.name} and I can fly!`);
}
}
}
Итак, теперь для создания собаки нам не нужно создавать отдельный класс, расширяющий Animal
, а можно изготовить его, передав класс Animal
в миксин Walker
:
const dog = new class extends Walker(Animal) { }('dog');
dog.walk(); // "I am a dog and I can walk!"
То же самое будет справедливо для рыбы и курицы, последняя получит два метода:
const fish = new class extends Swimmer(Animal) { }('fish');
fish.swim(); // "I am a fish and I can swim!"
const chicken = new class extends Flyer(Walker(Animal)) { }('chicken');
chicken.fly(); // "I am a chicken and I can fly!"
chicken.walk(); // "I am a chicken and I can walk!"
Более того, такой подход позволяет добавить еще больше свойств/методов на результирующий экземпляр и даже переопределить расширенные методы, давайте создадим утку и переопределим ее метод fly:
const proudDuck = new class extends Flyer(Swimmer(Walker(Animal))) {
public override fly(): void {
console.log('I don't fly as a chick, I fly like a duck!')
}
}('duck');
proudDuck.fly(); // "I don't fly as a chick, I fly like a duck!"
proudDuck.swim(); // "I am a duck and I can swim!"
proudDuck.walk(); // "I am a duck and I can walk!"
Что еще более интересно в этом подходе с использованием миксинов, так это то, что теперь мы можем создать даже летающую собаку, если захотим:
const flyingDog = new class extends Flyer(Walker(Animal)) { }('a flying dog!');
flyingDog.walk(); // "I am a a flying dog! and I can walk!"
flyingDog.fly(); // "I am a a flying dog! and I can fly!"
И мы по-прежнему сохраняем типы и intellisense. Удивительно, не правда ли? 🙂
P.S. код для статьи доступен здесь