Общий шаблон Javascript ES6 для большой иерархии
Я пытаюсь придумать универсальную функцию конструктора для большой иерархии объектов в Javascript. Цель состоит в том, чтобы реализовать как можно больше функциональных возможностей на вершине иерархии. Поработав немного, я получил структуру, которая мне нравится, хотя я не совсем доволен.
Структура в настоящее время выглядит примерно так. Я приложил рабочую (упрощенную) версию ниже:
class AbstractItem {
constructor(build) {
if (this.constructor === AbstractItem) {
throw new TypeError("Oops! AbstractItem should not be instantiated!");
}
this._id = build.id;
}
/*
The generic ItemBuilder is part of an abstract superclass.
Every item should have an ID, thus the builder validates this.
It also provides a generic build() function, so it does not have to be re-implemented by every subclass.
*/
static get Builder() {
/*
This constant references the constructor of the class for which the Builder function was called.
So, if it was called for ConcreteItem, it will reference the constructor of ConcreteItem.
This allows us to define a generic build() function.
*/
const BuildTarget = this;
class ItemBuilder {
constructor(id) {
if (!id) {
throw new TypeError('An item should always have an id!');
}
this._id = id;
}
//The generic build method calls the constructor function stored in the BuildTarget variable and passes the builder to it.
build() {
return new BuildTarget(this);
}
get id() {
return this._id;
}
}
return ItemBuilder;
}
doSomething() {
throw new TypeError("Oops! doSomething() has not been implemented!");
}
get id() {
return this._id;
}
}
class AbstractSubItem extends AbstractItem {
constructor(build) {
super(build);
if (this.constructor === AbstractSubItem) {
throw new TypeError("Oops! AbstractSubItem should not be instantiated!");
}
this._name = build.name;
}
/*
AbstractSubItem implements a different version of the Builder that also requires a name parameter.
*/
static get Builder() {
/*
This builder inherits from the builder used by AbstractItem by calling the Builder getter function and thus retrieving the constructor.
*/
class SubItemBuilder extends super.Builder {
constructor(id, name) {
super(id);
if (!name) {
throw new TypeError('A subitem should always have a name!');
}
this._name = name;
}
get name() {
return this._name;
}
}
return SubItemBuilder;
}
get name() {
return this._name;
}
}
class ConcreteItem extends AbstractItem {
doSomething() {
console.log('Hello world! My name is ' + this.id + '.');
}
}
class ConcreteSubItem extends AbstractSubItem {
doSomething() {
console.log('Hello world! My name is ' + this.name + ' (id: ' + this.id + ').');
}
}
new ConcreteItem.Builder(1).build().doSomething();
new ConcreteSubItem.Builder(1, 'John').build().doSomething();
На мой взгляд, у моего нынешнего подхода есть свои плюсы и минусы.
Pros
- Метод Builder() предоставляет общий интерфейс, который можно использовать для получения компоновщика для всех реализующих классов.
- Мои конкретные классы могут наследовать класс строителя без каких-либо дополнительных усилий.
- Используя наследование, конструктор может быть легко расширен при необходимости.
- Код компоновщика является частью абстрактного класса, поэтому понятно, что создается при чтении кода.
- Код вызова легко читается.
Cons
- Не ясно, глядя на функцию получения Builder(), какие параметры необходимы, чтобы избежать исключения. Единственный способ узнать это - взглянуть на конструктор (или на комментарии), который скрыт на несколько уровней.
- Это кажется нелогичным, если SubItemBuilder наследуется от super.Builder, а не класса верхнего уровня. Аналогично, другим может быть неясно, как наследовать от ItemBuilder, не глядя на пример SubItemBuilder.
- Глядя на класс AbstractItem, не совсем понятно, что он должен быть создан с использованием компоновщика.
Есть ли способ улучшить мой код, чтобы отрицать некоторые из упомянутых мною минусов? Любая обратная связь будет очень ценится.