Классы
Ответ прост: классы не строго необходимы для ООП. Знаю, это может шокировать.
Разумеется, нам нужна возможность создания новых объектов, и языки на основе классов, очевидно, распространены сильнее. Однако это не единственный способ достижения цели.
В языках наподобие JavaScript (хотя ES6 добавил в язык классы6, 7) и Lua используется концепция ООП на основе прототипов или прототипного ООП. Вместо создания схемы для конструирования новых объектов мы используем существующий объект в качестве прототипа. Такой подход даже может иметь реальные преимущества, поскольку снижает сложность языка8.
Тип данных :Class аналогичен словарю, но все его поля обязаны иметь имена (хотя доступ к свойствам класса по индексу так же возможен). При создании экземпляра класса, создается новая переменная, для которой копируются свойства и методы всех родителей.
Создание нового сложного типа (класса), происходит согласно правилам синтаксиса как создание функции в пользовательском пространстве имен. Имя функции является именем нового класса, а сама функция будет выступает в роли конструктора экземпляров класса.
Синтаксис создание класса выглядит следующим образом:
# Новый тип (класс) :NewClass
ns::NewClass() := :Class() { # Родительские класс или несколько классов через запятую
# Конструктор класса - весь блок кода с определением полей и методов
@::static := 1; # Поле класса (у всех объектов одно значение)
static := 1; # Поле класса (у всех объектов одно значение)
.field := 1; # Поле объекта
func() := {}; # Метод класса всегда статический
#.method() := {}; # Метод объекта (у каждого объекта свой)
~NewClass() ::= { # Деструктор класса
};
};
obj := ns::NewClass(); # Экземпляр класса
Так как NewLang реализует полный набор вариантов проверок при создании объектов, то переопределения наследуемых функций не требует никаких ключевых слов:
NewClass2() := NewClass() { # Новый класс на базе существующего
.field ::= 2; # Будет ошибка, т.к. поле field уже есть в базовом классе
method() = {}; # Аналог override, т.к. method должен существовать в базовом классе
};
Интерфейсы, именование методов классов и пространства имен
Для создания уникальных идентификаторов на основе имен методов классов, NewLang использует подход, похожий на применяемый в языке Python.
При создании метода класса, создается глобальная функция с именем класса и именем метода, объединенные через разделитель области имен.
Например, в классе NewClass2
при создании метода method
будет создана функция с именем ::NewClass2::method
.
Такая схема наименований методов полностью соответствует именованию функций в пространствах имен, и тем самым позволяет определять классы c чистыми виртуальными функциями (методами без реализации), а в последствии определять их в пространстве имен или указав полное имя в явном виде.
ns:: {
NewClass3 := NewClass() { # Новый класс на базе существующего
virtual() := _; # Виртуальный метод
};
};
obj := ns::NewClass3(); # объект создать нельзя, будет ошибка
# Определить функцию для виртуального метода
ns::NewClass3::virtual() := {};
obj := ns::NewClass3(); # ОК
Конструкторы, деструкторы и финализаторы у объектов
ns::NewClass() := Class() { # Новый класс на базе существующего
# Блок кода функции - конструктор объекта
__NewClass__() = {...}; # Метод с системным именем типа - деструктор объекта (вызывается сразу после удаления)
_NewClass() := {...}; # Скрытый метод с именем типа - защищенный конструктор объекта ?????????????????
__NewClass() := {...}; # Скрытый метод с именем типа - приватный конструктор объекта ?????????????????
:NewClass(type) ::= {...}:NewClass; # Функция для приведения типа объекта $type к типу :NewClass
~NewClass() = {...}; # Скрытый метод - финализатор (вызывается перед освобождением памяти)
__equals__(obj):Bool = { __compare__($obj) == 0 };
__compare__(obj):Int8 = { :: __compare__(@this, $obj) };
_() := {...}; # ?????????????????????????????????????
};
extension methods !
Пишу на С++ последние лет 20, до этого ещё Delphi лет 10. С годами полностью отошёл от ООП в сторону data-driven design. Классы — практически структуры, из методов как правило только декоративные геттеры. Всё остальное — это просто функции с понятными названиями, сигнатурами и операндами-объектами таких вот классов-носителей данных и состояния. Получается, очень легко дышится и чистенько — состояния изолированы в структуры, логика изолирована в функции. Конечно, в C++ всё довольно печально с ООП как таковым, поскольку нет механизма extension methods — это когда вы собираете методы в класс из разных единиц трансляции из-за этого, обычные функции C++ значительно удобнее методов класса
Основная мысль, которая почти всегда теряется при обсуждении ООП, это то, зачем он нужен и в чем его суть. Все сводится к инструментам, их правильном и неправильном использовании, практичных архитектурах и оверинженеринге.
Мне кажется самая главная мысль очень проста. Сложная программа это сложное состояние. Проблема в том, что сложно следить за всей возможной суперпозицией всех деталей состояния. Небольшое состояние гораздо проще валидировать и постоянно поддерживать целостным. Если ты собираешь сложное состояние из простых целостных состояний, то его тоже проще поддерживать целостным (потому что нужно следить только за высокоуровневым состоянием, но не за всеми деталями)
Соответственно ООП - это способ описания программы как набора иерархии изолированных состояний, где операции по изменению состояний максимально приближены к состоянию.
Точно так же как сложную функцию можно представить как последовательность простых, так и сложное состояние можно представить комбинацией простых состояний. Изоляция и контроль состояний и есть основная идея ООП.
ФП подходит с другой стороны - максимально старается избежать состояния и работать только с текущим контекстом. Подход не лишен логики, но любая программа - это прежде всего состояние.
Все остальное - лишь инструменты и особенности реализации. ООП может быть реализовано почти на любом языке самыми разными инструментами и не обязательно требует интерфейсов, классов, сообщений, инкапсуляции и т.д. Но разумеется современные ООП языки хорошо адаптированы для такого способа описания. В конечном итоге если подсказки или области видимости действительно не дают тебе менять состояние объекта - это и есть прямая польза на этапе понимания и доработки твоего кода. Тебе просто не нужно думать и знать о состоянии объекта, когда ты с ним не работаешь.
Главное в ООП - это то, что есть данные, и есть методы которые напрямую работают с этими данными. Это то, что можно засунуть в один объект. Другой объект, который хочет получить данные из первого объекта, обязан пользоваться публичными методами.
Это упрощает поддержку обратной совместимости, упрощает изоляцию объектов, упрощает версионирование в случае многократного использования одними объектами других объектов.
И опять таки, суть не в том, что это чем-то напоминает рест-апи или библиотеки, а в том, что это парадигма программирования, которая упрощает организацию разработки сложных проектов, в которых задействовано много разработчиков.
Все остальные штуки - наследование, интерфейсы и все другое - это не суть ООП, а различные варианты решения или оптимизации различных кейсов