Объектно-ориентированное программирование без классов
Июнь 2018
В современных объектно-ориентированных языках программирования фокус смещен с объектов и их общения на классы и типы данных. Но ООП можно реализовывать без классов, типов и даже без формальных объектов! По-сути объект является набором поведений со скрытым внутренним состоянием. Поведения – это те сообщения (методы) на которые объект может отвечать. Внутреннее состояние – это внутренняя кухня объекта, о которой никто снаружи не знает.
И набор поведений, и скрытое внутреннее состояние можно реализовать в любом языке программирования, который поддерживает first-class функции, замыкания и переопределение переменных. На языке JavaScript определение объекта без использования формального JavaScript-объекта будет выглядеть так:
function Note(title, text) {
function self(method) {
switch (method) {
case 'asString': return () => {
return `${title}\n\n${text}`;
};
case 'changeTitle': return (newTitle) => {
title = newTitle;
return self('asString')();
}
}
}
return self;
}
const note = Note('About OOP', 'Some text');
note('asString')();
/*
"About OOP
Some text"
*/
note('changeTitle')('OOP without classes');
/*
"OOP without classes
Some text"
*/Получился отличный объект: он предоставляет набор поведений, а клиент этого объекта может влиять на внутреннее только через сообщения. Создать приватный метод в таком объекте – раз плюнуть:
function Note(title, text) {
function self (method) {
switch (method) {
case 'asString': return format;
case 'changeTitle': return (newTitle) => {
title = newTitle;
return format();
}
}
}
function format() {
return `${title}\n\n${text}`;
}
return self;
}
const note = Note('About OOP', 'Some text');
note('asString')();
note('changeTitle')('OOP without classes');Такой подход создания объектов можно сделать намного дружелюбней, заменив switch-выражение на формальный JavaScript-объект. Не используя ни class, ни new, ни this, мы создаем прекрасные объекты:
function Note(title, text) {
const self = {
asString: format,
changeTitle: (newTitle) => {
title = newTitle;
return format();
}
};
function format() { return `${title}\n\n${text}`; }
return self;
}
const note = Note('About OOP', 'Some text');
note.asString();
note.changeTitle('OOP without classes');На самом деле, для создания объектно-ориентированных систем в наследовании нет необходимости. Большинство задач можно решить с помощью композиции, но если очень хочется использовать наследование, то это тоже возможно:
function Superman(name, age) {
const man = Man(name, age);
const self = {
whoAreYou: () => `I'm a ${age} years old superman!`,
}
return Object.assign({}, man, self);
}
function Man(name, age) {
const self = {
whoAreYou: () => `I'm a ${age} years old man`,
whatIsYourName: () => `My name is ${name}`,
}
return self;
}
const superman = Superman('Jack', 32);
const man = Man('Alex', 30);
superman.whoAreYou();
// "I'm a 32 years old superman!"
man.whoAreYou();
// "I'm a 30 years old man"
superman.whatIsYourName();
// "My name is Jack"
man.whatIsYourName();
// "My name is Alex"Последние два сниппета кода формально являются фабриками JavaScript-объектов, которые содержат в себе только методы и не дают напрямую управлять внутренним состоянием объекта и его приватными методами. По-сути же эти два сниппета – пример хорошего описанием объекта без использования class, new и this. Проще – лучше!
Если вам интересна тема ООП и его отличия от абстрактных типов данных, предлагаю почитать хороший paper, статью в блоге и книгу "Структура и Интерпретация Компьютерных Программ".