Я работаю над библиотекой, в которой я хотел бы поддерживать несколько разных расширений для одних и тех же библиотечных методов.
Вот мой укороченный код примеси, чтобы дать вам представление:
type Creator<T = {}> = new (...args: any[]) => T;
class DefaultCreator {
world:string[] = [];
someMagic() {
this.world.push("magic")
}
getWorldList() {
return this.world;
}
}
function PlantCreator<TBase extends Creator>(Base: TBase) {
return class extends Base {
private world = (<DefaultCreator><any>this).world;
createPlant() {
this.world.push("Plant")
}
};
}
function AnimalCreator<TBase extends Creator>(Base: TBase) {
return class extends Base {
private world = (<DefaultCreator><any>this).world;
createDog() {
this.world.push("Dog")
}
};
}
Я использую это так:
const MyWorld = AnimalCreator(PlantCreator(DefaultCreator));
const world = new MyWorld();
world.someMagic();
world.createPlant();
world.createDog();
Теперь мой вопрос: как я могу создать класс, взяв «Мой мир» сверху?
abstract class Playground {
abstract createWorld(creator: DefaultCreator);
play() {
this.createWorld(new DefaultCreator());
}
}
Моя идея заключается в том, что реализация может использовать функции фреймворка (здесь просто играйте) и создает мир с помощью настроенного Создателя (он же Builder). Я пробовал дженерики, но они не компилируются. Что я делаю неправильно?






Кажется, что это невозможно. Поскольку MyWorld не является типом, это скорее странное описание типа.
Когда я позволяю своей идее указать тип, я получаю эту строку:
const MyWorld: { new(): ({ world: string[]; createDog(): void } & any); prototype: { world: string[]; createDog(): void } } = AnimalCreator(PlantCreator(DefaultCreator));
Мое решение состоит в том, чтобы заставить пользователя по соглашению создать экземпляр пользователем и вернуть world моего примера.
Я изменил свою игровую площадку, чтобы получить этот код:
abstract class Playground {
abstract createWorld(): string[];
play() {
console.info("Creating world containing:");
this.createWorld().forEach(item => console.info(`- ${item}`))
}
}
class MyPlayground extends Playground {
createWorld(): string[] {
const world = new MyWorld();
world.someMagic();
world.createPlant();
world.createDog();
return world.getWorldList();
}
}
new MyPlayground().play();
Вывод кода выше:
Creating world containing:
- magic
- Plant
- Dog
Вы на самом деле были не за горами, но вы размыли границы между Типами и Ценностями. К счастью, Javascript/Typescript позволяет это сделать.
// Match only a constructor
type Creator<T = {}> = new (...args: any[]) => T;
// Match an actual class
interface CreatorClass<T> {
new(...args: any[]): T;
}
class DefaultCreator {
world:string[] = [];
someMagic() {
this.world.push("magic")
}
getWorldList() {
return this.world;
}
}
function PlantCreator<TBase extends Creator>(Base: TBase) {
return class extends Base {
private world = (<DefaultCreator><any>this).world;
createPlant() {
this.world.push("Plant")
}
};
}
function AnimalCreator<TBase extends Creator>(Base: TBase) {
return class extends Base {
private world = (<DefaultCreator><any>this).world;
createDog() {
this.world.push("Dog")
}
};
}
interface IPlantWorld {
createPlant(): void;
}
interface IAnimalWorld {
createDog();
}
const MyWorld: CreatorClass<IPlantWorld & IAnimalWorld> = AnimalCreator(PlantCreator(DefaultCreator));
abstract class Playground {
// I want to have a reference of the class' constructor
createOtherWorld<T>(creator: Creator<T>) {
return new creator();
}
// I want to reference the class itself
createWorld<T>(creator: CreatorClass<T>): T {
return new creator() as T;
}
play() {
this.createWorld(DefaultCreator);
}
}
class EverythingPlaygroundFactory extends Playground {
play() {
// provide the type information
return this.createWorld<IAnimalWorld & IPlantWorld>(MyWorld);
}
}
let pg = new EverythingPlaygroundFactory();
let world = pg.createWorld(MyWorld);
world.createPlant();
world.createDog();
pg.createOtherWorld(MyWorld.prototype.constructor);
Вероятно, это больше похоже на то, что вы искали.
Что следует отметить:
type Creator<T = {}> = new (... args: any[]) => T может и будет ссылаться только на фактический конструктор любого класса, но никогда не будет соответствовать всему классу/объекту класса. Всегда помните, что классы/функции являются исполняемыми объектами.
Я вернусь к вам через минуту. нужно возиться
Это должно решить проблему типа. Тем не менее, я настоятельно рекомендую пересмотреть свой шаблон проектирования, поскольку вы пытаетесь объединить фабрики и экземпляры, созданные фабрикой, в одно целое. Но одно наблюдение, которое я сделал, заключается в том, что всякий раз, когда вы «не видите» метод в Intellisense, это происходит потому, что не было доступной информации о типе во время компиляции. Вы можете решить эту проблему, определив информацию о типе в интерфейсе и указав ее в качестве аннотации возвращаемого типа.
Теперь вы ограничили игровую площадку растениями, я не могу создавать животных. Вот почему мне нравится идея примесей, которые обеспечивают своего рода полиморфизм, и декораторов, с помощью которых я могу вызывать все, что захочу, для одного объекта. Мой пример выше был не очень хорош, как я вижу сейчас.
Я рекомендую пересмотреть свой дизайн. Вы пытаетесь получить информацию времени компиляции, созданную во время выполнения. Если вы хотите объединить типы, вы можете использовать оператор &, который говорит: «Все оба типа доступны в этом».
Я обновил пример кода, чтобы продемонстрировать типы объединения и то, как сделать createDog и createPlant доступными. Теперь вы должны быть в состоянии экстраполировать все, что вам нужно понять из примера.
Я обновил свой ответ решением, которое у меня было, просто если вы хотите его сравнить. Я еще раз просмотрел ваш код, это ясный и не с моей точки зрения классический способ с интерфейсами, которые, на мой взгляд, не являются накладными расходами. Я думаю, что возьму свое собственное решение. Однако вы получите мой голос, так как мне нравится идея с оператором &.
Да, это лучший способ сделать это ☺️
Вы предлагаете интересное решение. Итак, вы помещаете в интерфейс только конструктор для создания новых экземпляров, верно? Если это правда: крутой трюк! Однако вы упустили тот факт, что Playground является абстрактным классом, каждая пользовательская реализация должна использовать своих собственных создателей. Когда я изменяю вашу функцию
createWorld, я не могу использовать методы, определенные вPlantCreatorиPlantCreator. Есть ли тоже решение?