Что такое ООП? Ожидаемый ответ, опять-таки: "наследование-инкапсуляция-полиморфизм". Три красивых длинных слова, отражающих некие абстрактные концепции, конечно, добавляют своего мистического флёра. Но я всё-таки рискну заметить, что суть ООП совсем не в этом. И у меня есть доказательства.
Начнём по порядку. Наследование, как выяснилось, бывает очень разное и вообще необязательно. Его доволно легко имитировать. Смотрите сами, код:
Код: "C"
class MyBase {
public MyBase(int param) {
// do constructor
}
public void MyMethod(String param) {
// do something;
}
}
class MyChild extends MyBase {
public MyChild(param) {
// do constructor 2
}
// other methods
}
по сути оказывается почти эквивалентен[3] коду следующему
Код: "C"
interface MyBase {
public void MyMethod(String param);
}
class MyParent implements MyBase {
public MyParent(int param) {
// do constructor
}
public void MyMethod(String param) {
// do something;
}
}
class MyChild implements MyBase {
MyParent _base;
public MyChild(param) {
MyParent _base = new MyParent(param);
// do constructor 2
}
public void MyMethod(String param) { return _base.MyMethod(param); }
// other methods
}
Т.е. наследование класса ("is-a") по сути есть комбинация реализации ("is-a") интерфейса и включения ("has-a") родительского объекта, которому "делегируется" обработка не-переопределённых методов. Как бы синтаксический сахар для такой комбинации.
Собственно, наследование, по факту, примерно так и реализуется C++/Java, если предствавить, что _base является не ссылкой, а типом-значением, а вместо пробрасывающих ("делегирующих") обёрток для MyMethod работает виртуальная таблица методов. (А в
Tcl/Snit оно вообще реализуется В ТОЧНОСТИ так.)
Поскольку "интерфейс" суть проявление полиморфизма - мы таки свели наследование к полиморфизму. Он, вообще говоря, тоже иногда существует вполне себе отдельно от ООП (см. полиморфные операторы в раннем Perl), но его пока отложим на потом.
С последним элементом, инкапсуляцией всё гораздо проще. Она попросту необязательна как таковая. Это продемонстрировал Python и другие скриптовые языки: отсутствие ограничений на доступ к полям и методам вовсе не приводит к катастрофам, ибо если разработчик таки добрался до недокументированного метода - скорее всего, он уже разобрался в коде и понимает, что он делает.
Сорвав лишнюю терминологическую шелуху, поглядим на объект и вспомним, что он всё-таки из себя представляет: это всего лишь
объединение данных и кода для работы с ними.
На все руки мастерОбъединив в себе структуру данных (кортеж) и набор функций, объект стал заменителем, с одной стороны, для кортежа, с другой стороны, для модуля. В PHP и Java, например, где другие средства организации модульности и пространств имён практически отсутствуют[4], класс вообще стал
синонимом заменителем понятия "модуль". А модульность - это очень полезный подход, который позволяет делать код более читабельным и поддерживаемым.
С одной стороны, казалось бы, это чисто терминологический вопрос. Ну слили структуру и модуль вместе, получили более универсальную единицу организации кода - вроде нормально.
С другой стороны, выяснилось, что даже после такого слияния классы стремятся расползтись в разные стороны, согласно первоначальным предназначениям: возникают "классы-модули", напичканные огромным количеством методов, в т.ч. статических, и склонные к злоупотреблению паттерном Singleton; и "классы-кортежи", содержащие в себе только поля и набор тупых геттеров-сеттеров[5]. Всё это называется "анемичная модель", о которой уже упоминалось выше.
Анемичная модель диктуется практикой. Она решает упомянутую выше проблему "ручка.писать(бумажка, текст) VS бумажка.писать(текст, ручка)". В самом деле, основное отличие метода от процедуры в том, что 1) один из аргументов передаётся как "this" и для него позволен доступ к приватным атрибутам; 2) метод приписывается к определённому классу (т.е. пространству имён). Пункт 1, при условии отказа от инкапсуляции, перестаёт быть критичным. А для пункта 2 отнюдь не всегда разумно выбирать класс одного из аргументов, гораздо чаще здравый смысл подсказывает использовать для этого некий другой класс-"модуль". Всё это подозрительно напоминает старый добрый процедурный подход (с точностью до полиморфизма, но о нём см. ниже).
Таким образом, ООП, стремясь заменить собой структурно-процедурное программирование, в конечном итоге вернулось практически к нему же, но только в модных обёртках. Возникает некоторый вопрос: а был ли вообще смысл в манёвре?..