Как проверить значение переменной, что в ней хранится класс который был наследован не формируя его экземпляр
Давайте представим себе такую абстракцию, где у нас есть базовый класс сущности AppEntity от него могут наследоваться всё остиальные, а также коллекция которая использует метод search для получения нужных сущностей по API, а далее лепить слепки данной сущности на основе переданного определенного класса в данной коллекции.
Загадка в том, что не совсем понятно как в JS правильно определить, что в свойстве экземпляра Collection храниться именно унаследованный класс от определенной сущности.
Исходные данные
1. Определения исходного класса коллекции:
class Collection {
/**
* @type {AppEntity|null} Класс сущности коллекции.
*/
entityClass = null
/**
* Выполняет запрос на получение данных коллекции
*
* @return {Promise<this>}
*/
async search() {
this.checkEntityClassIsDefined();
//Скрытый процесс получения данных
return this;
}
/**
* Проверяет, определен ли класс сущности.
*
* @return {void}
*/
checkEntityClassIsDefined() {
if (this.entityClass === null || !(typeof this.entityClass === 'function')) {
throw new Error(`Класс сущности не определен.`);
}
}
/**
* Устанавливает сущность коллекции.
*
* @param {AppEntity} entityClass Сущность коллекции.
*
* @return {this} Экземпляр текущей коллекции.
*/
forEntity(entityClass) {
this.entityClass = entityClass;
return this;
}
}
2. Определения исходного/общего класса сущности:
class AppEntity {
/**
* @type {{}} Атрибуты сущности и их значения.
*/
attributes: {}
/**
* Заполняет данные в сущность (переопределяя текущие).
*
* @return {this}
*/
fill();
/**
* Возвращает значения сущности..
*
* @return {object}
*/
pull();
/**
* Очищает атрибуты.
*
* @return {this}
*/
purge();
}
3. Формирование цепочки наследования от исходного/общего класса сущности:
class PostEntity extends AppEntity {}
Вопрос звучит следующим образом:
Как правильно определить, что в свойстве entityClass экземпляра Collection хранится класс который в свою очередь был наследован от AppEntity в методе checkEntityClassIsDefined?
Желательно не прибегая к формированию экземпляра сущности и проверки его через instanceof?
- Исполнения кода который не должен вызвать проблем, однако он не даёт уверенности, что мы действительно передали нужный нам класс, да и вообще если в переменную определить обычную функцию, то код исполнится:
const collection = (new Collection()).forEntity(PostEntity);
collection.search();
- Исполнения кода, где определено в место класса обычная стрелочная функция.
const collection = (new Collection()).forEntity(() => null);
collection.search();
- Исполнения кода, где не определен класс сущности в коллекции и ожидаем исключение.
const collection = (new Collection());
collection.search();
Ответы (1 шт):
Кропотливый анализ позволил сделать вывод, что есть 2 способа для получения прототипа объекта, связанного с переменной. Если полученный прототип является классом, то мы можем быть уверены, что переменная хранит в себе класс ну и в таком духе.
- Object.getPrototypeOf(variable) === AppEntity
- AppEntity.isPrototypeOf(variable)
А вот и тесты которые подтверждают данную теорию:
class PostEntity extends AppEntity {};
const VAR_POST_CLASS = PostEntity;
const VAR_POST_CLASS_INSTANCE = new VAR_POST_CLASS();
const VAR_POST_CLASS_EXTENDS_APP_ENTITY = class PostEntity extends AppEntity {};
const VAR_POST_CLASS_EXTENDS_APP_ENTITY_INSTANCE = new VAR_POST_CLASS_EXTENDS_APP_ENTITY();
const VAR_NULL = null;
const VAR_STRING = '';
const VAR_OBJ = {};
const VAR_NUMBER = 1;
const VAR_ARRAY = [];
const VAR_FUNCTION = () => {};
describe('Проверка Object.getPrototypeOf(variable) === AppEntity', () => {
let variableIsPrototypeOfAppEntity = (variable) => Object.getPrototypeOf(variable) === AppEntity;
it("Должно вернуть true, если переменная в которой храниться 'class PostEntity' наследуется от 'AppEntity'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_POST_CLASS_EXTENDS_APP_ENTITY)).toBe(true);
});
it("Должно выбросить исключение типом 'TypeError', если значение переменной является 'null'", () => {
expect(() => variableIsPrototypeOfAppEntity(VAR_NULL)).toThrow(TypeError);
});
describe("Должно вернуть false, если в переменной не храниться class который наследуется от 'AppEntity'", () => {
it("Проверяет экземпляр класса который не наследуется от 'AppEntity'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_POST_CLASS_INSTANCE)).toBe(false);
});
it("Экземпляр класса который наследуется от 'AppEntity'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_POST_CLASS_EXTENDS_APP_ENTITY_INSTANCE)).toBe(false);
});
it("Значение переменной является типом 'string'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_STRING)).toBe(false);
});
it("Значение переменной является типом 'object'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_OBJ)).toBe(false);
});
it("Значение переменной является типом 'number'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_NUMBER)).toBe(false);
});
it("Значение переменной является типом 'array'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_ARRAY)).toBe(false);
});
it("Значение переменной является типом 'function'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_FUNCTION)).toBe(false);
});
});
});
describe('Проверка AppEntity.isPrototypeOf(variable)', () => {
let variableIsPrototypeOfAppEntity = (variable) => AppEntity.isPrototypeOf(variable);
it("Должно вернуть true, если переменная в которой храниться 'class PostEntity' наследуется от 'AppEntity'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_POST_CLASS_EXTENDS_APP_ENTITY)).toBe(true);
});
describe("Должно вернуть false, если в переменной не храниться class который наследуется от 'AppEntity'", () => {
it("Проверяет экземпляр класса который не наследуется от 'AppEntity'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_POST_CLASS_INSTANCE)).toBe(false);
});
it("Экземпляр класса который наследуется от 'AppEntity'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_POST_CLASS_EXTENDS_APP_ENTITY_INSTANCE)).toBe(false);
});
it("Значение переменной является 'null'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_NULL)).toBe(false);
});
it("Значение переменной является типом 'string'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_STRING)).toBe(false);
});
it("Значение переменной является типом 'object'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_OBJ)).toBe(false);
});
it("Значение переменной является типом 'number'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_NUMBER)).toBe(false);
});
it("Значение переменной является типом 'array'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_ARRAY)).toBe(false);
});
it("Значение переменной является типом 'function'", () => {
expect(variableIsPrototypeOfAppEntity(VAR_FUNCTION)).toBe(false);
});
});
});