?

Log in

Кризис объектно-ориентированного программирования. - отражение жизни в экране монитора [entries|archive|friends|userinfo]
отражение жизни в экране монитора

[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Кризис объектно-ориентированного программирования. [Jun. 21st, 2010|01:47 pm]
отражение жизни в экране монитора
[Tags|, ]

Давно хотел про это написать и, наконец, руки дошли.

Повсеместное торжество объектно-ориентированного подхода - это реальность, данная нам в ощущениях.

На мой взгляд, это очевидно и неоспоримо. Подавляющая часть прикладной разработки ведётся на объектно-ориентированных языках и в объектном стиле. Рекрутеры ищут три заветные буквы в резюме[1]. Студентов программерских факультетов натаскивают на святую троицу "наследование-инкапсуляция-полиморфизм", чтоб от зубов отскакивала. Паттерны и рефакторинги считаются за заповеди, помнить их все наизусть (или хотя бы держать потёртые Евангение от Четырёх и томик Фаулера поближе к рабочему столу) - хороший тон.

Короче, всё объектно-ориентированное по умолчанию считается хорошим, а не-объектно-ориентированное - плохим и устаревшим. Сей консенсус плотно укоренён в головах большинства участников игры: от вяльяжного CEO на презентациях до щуплого кодера где-то на просторах бангалорщины. Слава богу, хотя бы ранняя восторжённая риторика на тему "объектный подход разумен и верен, потому что он отражает устройство материального мира" уже практически сгинула. Всем очевидно, что будь мир устроен аналогично объектно-ориентированным средам, письменность, например, никогда бы не была изобретена: человечество ещё на ранних этапах погрязло бы в бесконечных священных войнах, выясняя, как же всё-таки правильно: "ручка.писать(бумажка, текст)" или "бумажка.написать(текст, ручка)"[2].
Поэтому ООП было низведено с постамента Божественного Откровения до статуса "Метода, который Работает", эдакой как-бы-серебряной пули. Тоже неплохое положение, и угроз ему в ближайшее время не видно.

Тогда с чего я вообще завёл разговор о каком-то "кризисе"? А вот как раз ещё раз ухвачусь за пример с письменностью и акцентирую внимание на следующем: в объектном лагере согласия нет. Видны тёрки и непонимания между сторонниками различных конфессий диалектов и вариациий объектного подхода. Можно оценивать это по-разному, но лично мне кажется, что это "жжж" - неспроста.

Видовое разнообразие

О первом очевидном расколе в объектном движении я уже писал раньше, но это было настолько претенциозно, уныло и сумбурно, что, пожалуй, "проще новых наделать". Речь тогда шла о расхождениях между наиболее популярной "классовой" объектной моделью, приехавшей к нам как есть из языка Simula, и более гибкой прототипной моделью наследования, каковая характерна для скриптовых языков. Если Python, например, приложил определённые усилия к тому, чтобы с минимальными жертвами сделать свои объекты визуально совместимыми с Simula-подходом, то JavaScript и Lua заморачиваться не стали и ведут себя не вполне ожидаемым (для выходцев из мира Java/C++) образом. Попытки утрамбовать их в прокрустово ложе классов, наследования и аксесоров выглядят диковато и приводят только к распуханию и усложнению программ, не давая особой выгоды. Тем не менее, прототипная модель ничуть не менее "объектна", чем классовая. Но в ней многие рефакторно-паттерновые подходы начинают видоизменяться или вовсе пробуксовывать. Федот, да не тот.

Вопрос механизмов наследования - он вообще довольно больной. Даже между такими флагманами, как C++ и Java, нету согласия по вопросу множественного наследования и интерфейсов. Масла в огонь подлили дженерики, со своими запутанными правилами ковариантной/контравариантной совместимости. Недавно появившийся и успевший нашуметь гугловский Go напугал традиционалистов отказом от наследования вообще. На периферии маячат упомянутая выше прототипная модель, миксины, экстеншн-методы, multiple dispatch и прочая чертовщина. Как соблюсти чистоту канона (и где вообще этот канон) - непонятно совершенно.

Ещё один, недавно возникший раскол - противостояние "жирной" (rich) и "тощей" (anemic) моделей. В последнее время анемичная модель действительно стала набирать обороты. Казалось бы, ну и фиг - какая разница, это ведь всего лишь вариация на ту же самую объектную тему?.. Сейчас я попробую объяснить, почему мне это кажется серьёзной идеологической бомбой под фундаментом "классического" ООП.

Вторичность половых признаков

Что такое ООП? Ожидаемый ответ, опять-таки: "наследование-инкапсуляция-полиморфизм". Три красивых длинных слова, отражающих некие абстрактные концепции, конечно, добавляют своего мистического флёра. Но я всё-таки рискну заметить, что суть ООП совсем не в этом. И у меня есть доказательства.

Начнём по порядку. Наследование, как выяснилось, бывает очень разное и вообще необязательно. Его доволно легко имитировать. Смотрите сами, код:
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] коду следующему
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 отнюдь не всегда разумно выбирать класс одного из аргументов, гораздо чаще здравый смысл подсказывает использовать для этого некий другой класс-"модуль". Всё это подозрительно напоминает старый добрый процедурный подход (с точностью до полиморфизма, но о нём см. ниже).

Таким образом, ООП, стремясь заменить собой структурно-процедурное программирование, в конечном итоге вернулось практически к нему же, но только в модных обёртках. Возникает некоторый вопрос: а был ли вообще смысл в манёвре?..

Ещё раз о полиморфизме

Вышеприведённые рассуждения подталкивают нас к страшной мысли: единственное концептуальное и практически полезное отличие объектного программирования от комбинации "процедурное + модульность" в конечном сводится к наличию полиморфизма - т.е. способности объектов по-разному реагировать на сообщаемые им "сигналы" (вызовы методов).

Чтобы понять, в чём заключается особая магия полиморфизма, подумаем (как и в случае с наследованием), как бы мы его могли имитировать при помощи подручных средств.

Итак, рассмотрим следующий пример:
interface List {
    public int getLength();
}

class ArrayList {
    public int getLength() {
        // blah-blah вернуть хранимую длину
    }
    // blah-blah длина массива и ссылка на данные
}

class LinkedList {
    public int getLength() {
        // blah-blah побежать по узлам и сосчитать
    }
    // blah-blah ссылка на первый узел
}

Он реализуется в процедурном языке примерно как-то так:
struct {
    int typeCode;
    void* object;
} List;

const TYPE_ArrayList = 0;
const TYPE_LinkedList = 1;

int ArrayList_getLength(ArrayList* this) {
    // blah-blah вернуть хранимую длину
}

int LinkedList_getLength(LinkedList* this) {
    // blah-blah побежать по узлам и сосчитать
}

int List_getLength(List this) {
    if (this.typeCode==TYPE_ArrayList) return ArrayList_getLength((ArrayList*)this.object);
    if (this.typeCode==TYPE_LinkedList) return LinkedList_getLength((LinkedList*)this.object);
    // blah-blah ошибка типа
}

Любой уважающий себя программист, конечно, перепишет последнюю функцию так:
int List_getLength(List this) {
    switch (this.typeCode) {
        case TYPE_ArrayList:
            return ArrayList_getLength((ArrayList*)this.object);
        case TYPE_LinkedList:
            return LinkedList_getLength((LinkedList*)this.object);
    }
    // blah-blah ошибка типа    
}
А хитрый программист вообще постарается использовать таблицу:
(int(void*))[] VTABLE_getLength = {ArrayList_getLength, LinkedList_getLength};

int List_getLength(List this) {
    VTABLE_getLength[this.typeCode](this.object);
}

Но удобнее всего, конечно, когда полиморфизм встроен в язык и диспетчеризация производится неявно - т.е. первоначальный вариант.

Если мы выстроим все возможные способы реализации полиморфизма в ряд, по возрастанию их "удобства", получим: if < switch/case < таблица < ООП.

Теперь поразмыслим: какое качество возрастает в этом ряду слева направо? Ответ прост: декларативность. Т.е. отсутствие необходимости делать все проверки и переходы руками. switch/case избавляет от повторения названия проверяемой переменной, таблица избавляет от явного повторения вызова, а поддержка полиморфизма на уровне языка избавляет от необходимости явно указывать таблицу.

Декларативность сокращает и упрощает код. Вместо того, чтобы делать все мелкие операции руками, мы всего лишь задаём набор правил, которые потом обрабатывает некая хитрая подсистема. Меньше кода, меньше повторений, меньше шансов на ошибку, легче поддержка.

Конечно, как показывает пример SQL или чисто-функциональных языков[6], декларативность накладывает свои ограничения: вы не можете контролировать вычисления напрямую и вынуждены прибегать к фокусам для реализации некоторых достаточно типовых, но "непредусмотренных" данным декларативным подходом вещей. Но, как правило, декларативность всё-таки представляет собой благо.


Выводы

Что-то я написал, написал, пора и закругляться. Суммируем поток сознания:

  • наследование и инкапсуляция не являются обязательными и определяющими для ООП;
  • ООП характеризуется объединением данных и кода и полиморфизмом;
  • модульность - это полезно;
  • классы заменили собой кортежи и модули, но постепенно превращаются либо в одни, либо в другие;
  • таким образом, основное отличие ООП от процедурно-структурного - наличие полиморфизма;
  • полиморфизм есть частный случай декларативного программирования;
  • декларативное программирование нам строить и жить помогает.

Финальный вывод из всего этого следует такой:
Основная причина эффективности объектно-ориентированного программирования - в том, что оно содержит в себе средства обеспечения модульности и декларативности. Именно модульность и декларативность являются средствами, повышающими эффективность разработки - т.е. "как-бы-серебряными пулями". Именно на них надо ориентироваться при выборе методологии. Само по себе ООП являться целью не должно.
 
(Следует признать, что, действительно, организационные вопросы обычно влияют на скорость и качество разработки гораздо больше технических - но мы сейчас всё-таки о последних).


Примечания.

1 ^ Личный опыт общения по телефону: "так, а с ООП вы хорошо знакомы?" - "ну вот же, в резюме, два года опыта на C#, это ведь объектный язык, там просто по-другому нельзя!" - "ну вобщем-то верно, но... всё-таки, знаете ли вы ООП?!"

2 ^Было бы ещё радикальное движение, продвигающее подход "текст.написаться(бумажка, ручка)", но оно было бы заклеймлено опасной ересью сразу.

3 ^Интересные эффекты происходят, если MyMethod в базовом классе вызывает какие-то другие виртуальные методы объекта, которые в потомке переопределены; но это, вообще, довольно нездоровый способ организации кода и вместо него лучше прибегать к более явному Dependecy Injection.

4 ^Пакэджи в жабе в конечном итоге сводятся к классам, а неймсмпейсы в пхп появились только совсем недавно.

5 ^Наличие в Java прямого доступа к полям объекта и отсутствие properties - это вообще цырк какой-то. Они вообще высокоуровневый язык делали или где?!

6 ^Функциональное программирование является частным случаем декларативного, я настаиваю!
linkReply

Comments:
[User Picture]From: jakobz
2010-06-21 06:05 pm (UTC)
Типа того, да.
(Reply) (Thread)
[User Picture]From: b00ter
2010-06-21 06:21 pm (UTC)
О, да. Сейчас ковыряюсь в AS3-коде - мата не хватает из-за того, что из плагина, который пишу, до основного функционала не достучаться. Потому как оно все private или protected. И не выгружается. И частично не меняется. И вставить в основной код хак нельзя - потому как уплочено за ads-free версию, поставляемую бинарником.
(Reply) (Thread)
[User Picture]From: rainman_rocks
2010-06-21 06:43 pm (UTC)
Инкапсуляция = BDSM.
(Reply) (Parent) (Thread) (Expand)
[User Picture]From: tonsky
2010-06-22 03:28 am (UTC)
мнээ вступление обещало кризис с разоблачением, в выводах видим «Основную причину эффективности объектно-ориентированного программирования», а кризис-то где, кризис давай!!!11одинодин
(Reply) (Thread)
[User Picture]From: rainman_rocks
2010-06-22 07:20 am (UTC)
Заголовок был выбран скорее для сенсационности :]
"Кризис", если таковой и есть, выражен в размытии понятия и постепенном переходе на "анемичную" модель.
(Reply) (Parent) (Thread)
From: ext_72902
2010-06-22 08:05 am (UTC)
Очень неплохо написано, практически со всем согласен.

Вроде, кто-то мне говорил, что при добавлении дженериков в жабу вполне серьёзно рассматривался такой вариант: сделать дженерики, оставить интерфейсы, выкинуть на фиг (!) наследование. Не знаю, правда или нет, но на такой язык я бы посмотрел с баальшим интересом.
(Reply) (Thread)
[User Picture]From: rainman_rocks
2010-06-22 08:12 am (UTC)
Довольно сомнительная легенда, по ряду причин:
- женерики в жабе были реализованы сначала сторонней командой Одерского в виде отдельных языков (Pizza, Generic Java) и потом только с большой неохотой и оговорками влеплены в основной язык
- сан вообще ОЧЕНЬ не хотела производить сильные изменения в жабе. Поэтому даже женерики были сделаны с type erasure, т.е. с минимальными изменениями в VM
- Гослинг сам признавался, что он одержим производительностью. Виртуальные методы в интерфейсах, увы, несколько медленнее, чем в простом наследовании (там либо очень разбухшие, либо двухуровневые виртуальные таблицы получаются); на такую потерю он бы не пошёл никогда.
(Reply) (Parent) (Thread) (Expand)
(Deleted comment)
[User Picture]From: rainman_rocks
2010-06-23 03:40 pm (UTC)
Ну, вообще-то, то, что вы написали - это примерно инкапсуляция и есть.
(Reply) (Parent) (Thread)
(Deleted comment)
[User Picture]From: max630
2010-06-23 07:29 pm (UTC)
мне кажется, там главное не "удобство", а то что реализацию можно динамически, не меняя кода интерфейса, расширять.

ну и с инкапсуляцией не - так быстро... ну и что что в питоне можно доступиться к приватам? можно подумать в c++ нельзя. главное что этого почти никто не делает.
(Reply) (Thread)
[User Picture]From: rainman_rocks
2010-06-23 07:31 pm (UTC)
А вы в жабе, в жабе попробуйте.
(Reply) (Parent) (Thread) (Expand)
[User Picture]From: love5an
2010-07-14 01:28 am (UTC)
С ООП все просто; это, если суммировать высказывания Алана Кея, например - объекты(т.е. состояние) плюс их взаимодействие. Наследование - действительно довольно побочная вещь. Как и полиморфизм. А вот инкапсуляция - вещь довольно важная; но, конечно, именно инкапсуляция, а не такая глупая концепция, как сокрытие данных с помощью private/protected.

Вообще, ООП это очень неплохо. Плохи его реализации в мейнстримных языках - C++/Java/C#/Python/PHP/Perl/Ruby/etc. Даже не просто плохи, а полное говно из себя представляют.
Другое дело - Common Lisp(CLOS), да и даже сам дедушка Smalltalk. К ним вся эта критика(?) и выводы малоприменимы(ну кроме как про декларативность; вот тут - да).
(Reply) (Thread)
From: trifon_levecke
2010-09-12 06:44 am (UTC)
клёва . rainman_rocks
(Reply) (Thread)
From: rainbow_beast
2010-09-23 05:49 pm (UTC)
Плюс объектности как раз в том, что если надо, можно на декларативность забить иделать всё в чисто процедурном стиле. Ну и в том, конечно, что это такая форма декларативности, которая (как минимум, в Simula-инкарнации) хорошо ложится на реальное железо. В котором таки ячейке памяти можно много раз присвоить значение :-)
(Reply) (Thread)
[User Picture]From: rainman_rocks
2010-09-23 09:34 pm (UTC)
Я вижу дефекты логики в обоих ваших посылках.

Первый - ну так и в необъектных функциональных языках можно таки лабать императивный код. Объекты тут вообще непричомъ.

Второй - нет, как раз в том месте, где мэйнстрим-ООП декларативно, оно и иммутабельно. Вы не можете стандартными средствами крестов или жабы поменять у уже созданного объекта класс или изменить поведение одного из его методов.
(Reply) (Parent) (Thread)
[User Picture]From: _winnie
2010-09-26 10:42 am (UTC)
> как же всё-таки правильно: "ручка.писать(бумажка, текст)" или "бумажка.написать(текст, ручка)"
Заморочился с этой задачей.
Проследим за естесвенным языком, и посмотрим на буквальный смысл:

"Бумага написала текст ручкой на себе" - бред.
"Ручка написала текст на бумаге" - бред. волшебная ручка, сама пишет?
"Текст написался ручкой на бумаге" - бред.

Вот так более реалистично:
"Чукче-писателю дали бумагу, и он написал ручкой текст на ней" - вроде ok.


import Image, ImageDraw

text = "hello, world"
bumaga = Image.new("RGB", (200, 200))
ruchka = (255, 128, 0) #r,g,b

chukcha = ImageDraw.ImageDraw(bumaga)
chukcha.text((100, 100), text = text, fill = ruchka)

bumaga.show()


(Reply) (Thread)
[User Picture]From: rainman_rocks
2010-09-26 10:44 am (UTC)
На мой взгляд, правильнее было бы вызывать функцию из модуля Чукча.
(Reply) (Parent) (Thread)
(Deleted comment)
(Deleted comment)
(Deleted comment)
(Deleted comment)
(Deleted comment)
[User Picture]From: thornik
2010-10-04 09:41 pm (UTC)

Читал и завис...

"Поскольку "интерфейс" суть проявление полиморфизма - мы таки свели наследование к полиморфизму".

Интерфейс - это контракт, как он может быть ПОЛИ? Полиморфный - имеющий много форм, это притянутый косноязычным Трупостраусом термин для многоПОВЕДЕНЧЕСКИХ функций. Я что-то не понял в жизни?

Наследование - конечно не обязательная часть ООП, но как средство реюзабельности - превосходное, тем более так красиво вписанное в теорию.

> классы заменили собой кортежи и модули, но постепенно превращаются либо в одни, либо в другие;

Ни разу не так - это просто частные маргинальные случаи.

> таким образом, основное отличие ООП от процедурно-структурного - наличие полиморфизма;

Узколобо. Классы могут не только перекрывать метод Draw, но и перемещаться между методами, давая дёргать разные функции, причём сами методы даже не узнают, кто по ним пробежал. Т.е. этот шмоток кода и данных намного гибче типов и модулей функций.

ООП убил урод-С++ с братом-гидроцефалом C#, настоящий ООП похоронен вместе со Смоллтоком.
(Reply) (Thread)
[User Picture]From: rainman_rocks
2010-10-04 10:27 pm (UTC)

Re: Читал и завис...

Интерфейс - контракт. Полиморфизм - его реализации. Они всегда существуют вместе и бессмысленны по отдельности.

Ничего в наследовании особо связанного с реюзабельностью и красотой не нахожу. Единственные плюсы наследования по сравнению с интерфейсным полиморфизмом - сокращённое описание и вычислительная простота.

> Классы могут не только перекрывать метод Draw, но и перемещаться между методами, давая дёргать разные функции, причём сами методы даже не узнают, кто по ним пробежал.
Вы не могли бы выразиться попонятнее? Что значит "перемещаться между методами", что значит "дёргать", что значит "пробежал по методам"? Терминологический мрак.
(Reply) (Parent) (Thread) (Expand)
[User Picture]From: sanmai
2010-12-17 06:29 am (UTC)
Спасибо за пост, хорошо написано. Обсуждение выше доставляет не меньше.
(Reply) (Thread)
(Deleted comment)
From: muradov
2012-03-23 08:03 pm (UTC)
Процедуры это вообще не заслуга процедурного программирования. Ведь то же самое можно сделать и на асме! )))
(Reply) (Thread)
[User Picture]From: rainman_rocks
2012-03-24 10:13 am (UTC)
что угодно можно сделать на чём угодно, было бы время
(Reply) (Parent) (Thread)
[User Picture]From: dima78
2012-09-24 04:09 pm (UTC)
дело не в недостатках ооп, не в кризисе, а в том, что его как такового и не было (ну может за исключением каких-то специфических языков, тут я не спец)

не было ооп в том смысле, что реализуются объекты реального мира, которые общаются друг с другом, ни концептуально, ни в реализации

на деле что было – попытка натянуть красивую идею на языки-наследники машинного кода и привязать эти идеи к фоннеймановской архитектуре, чтобы оно было поближе к реализации и пооптимальнее. и в дотнетах, от версии к версии вводятся вроде бы простые и понятные концепции, но на них опять же висит столько груза совместимости с предыдущими версиями и реализацией, что переварить их бывает трудно (как собственно объяснение и работа этих новых фич отталкивается от этой совместимости и реализации)

реализация

наследование реализации в классах – это просто супермегажесть. в большинстве случаев, надо перекрыть всего-лишь какой нибудь вшивый обработчик событий, для чего в ранних реализациях предлагалось плодить классы-наследники, перекрывать нужный метод и пользоваться объектом-наследником. благо потом от этого отошли, в сторону анонимных делегатов и анонимных интерфейсов (оно же обработчики событий, мультикастные делегаты, колбэки, обработка обновлений/реактивации), функциональщина/прототипирование в чистом виде ;)

далее, чтобы сделать возможным использовать возможность наследования реализации и аккуратно дополнить иерархию своими элементами, приходилось плодить развесистую клюкву классов, и тонко размазывать функционал/данные по ней (ведь не знаешь куда захочет вклиниться пользователь), а пользователю – вникать во всю эту клюкву. причём вся эта иерархия (и взаимосвязи!) хардкоднута, со всеми вытекающими.

поэтому наследование/виртуальные функции если и нужны (в таком хардкоднутом и явно выписанном виде, см. далее), то в очень ограниченном количестве случаев. (ну и типа наследование – это частный случай интерфейсов и аггрегации)

хорошо что сегодня от этого отошли в сторону анонимных интерфейсов/делегатов, но местами эти ужасы сохранились, и когда приходится влезть в такую цепочку, чтобы сделать банальную вещь, процесс превращается в ужас-ужас.

концепция

на самом деле, то что внутри, это что угодно, но никак не моделирование объектов реального мира (или чего-то похожего), которые общаются друг с другом.

оно отталкивается от структуры данных, от алгоритмов, от каких-то деталей внутренней реализации, но это никак не красивые объекты, которые общаются друг с другом. и на всё это слегка натянута ИНП-троица ООП, хотя на самом деле нужна модульность и те самые анонимные делегаты/интерфейсы (или по другому функции как объекты первого класса).

по моему опыту, в большинстве случаев нужны именно что анемичные классы, т.е. запакованные в модули данные/функции с выставленными наружу делегатами/интерфейсами. которые тупо отталкиваются от тех-же данных, алгоритмов и реализации. и поэтому плодить кучу классов-обёрток, чтобы перекинуть туда-сюда массив байт (пусть даже с разной сутью содержимого) – это жесть. и если пользователю этих классов надо что-то поменять в глубине системы, то уж проще ему отдать незамутнённые клюквой исходники. пусть даже он сделает собственный, слегка видоизменённый, клон, скопировав большинство исходного кода неизменным.

и опять, если какой фанат решит применить концепцию напрямую, что объекты – это такие самостоятельные сущности, которые самостоятельно общаются друг с другом, получится развесистая (хардкоднутая!) клюква классов, с которой не то чтобы что-то сделать, понять будет трудно.

и все эти дотнеты и жавы, по большей части и есть модульность (даже и понятней было бы большинство классов так и называть – модулями), слегка завёрнутая в ИНП-троицу (или скажем так, рядовой пользователь ими именно так и пользуется, и с внутренней клюквой дела практически не имеет). и это хорошо, что только слегка, спасает нехилый закос в функциональщину, иначе это был бы реальный трындец!

да-да-да, перефразируя одного товарища, мне деньги надо зарабатывать, а не плодить развесистые клюквы классов (и не ломать голову в чужих), а тому, кто захочет насадить мне все эти клюквы, не платя за это – с разгону ногой по ... ну вы поняли ;)

...продолжение следует...
(Reply) (Thread)
[User Picture]From: dima78
2012-09-24 04:10 pm (UTC)
...

конечно, можно сделать собственную надстройку над языком (здесь каким-то боком слегка отдаёт паттернами проектирования), или даже движок, который бы исходную концепцию понимал как надо, действительно представлял бы объекты, их взаимодействие в чистом виде, да без жестоких хардкоднутостей. но это уже будет никаким боком к современным ООП-средам, т.к. в обоих случаях это тупо отдельный движок, в первом случае, правда, при помощи самой среды, реализующий себя ближе к рантайму.

что у нас в итоге

то есть ни реализационно (дизайн _не_ базируется на ИНП-тройке в чистом виде), ни концептуально (объекты _не_ сущности реального мира, а что-то совсем иное, привязанное к особенностям архитектуры) в ООП по большей части нет

ИНП-троица в итоге отдыхает, на самом деле нужна модульность и функциональщина, в реальности так и есть (хоть оно и декларируется как ООП), хотя хватает и клюквы, и прочих загонов на совместимость.

что в принципе для незамутнённого догмами разума вполне юзабельно, что впрочем и пытались довести выше некоторые товарищи, пытаясь налепить на этот итог вывеску ООП (реклама собирает свой урожай :( ).

но хочется большего. как минимум всё-таки разгрузится от этой ботвы с совместимостью с наследниками машинных кодом и слегка оторваться от внутренней машинной реализации. как максимум – поиметь чёткий и ясный инструмент для работы с заявленной концепцией в чистом виде.

куда дальше

а дальше хочется много вкусного и интересного

хотя бы той же самой концепции, про объекты-сущности и их незамутнённое общение. пусть не реального мира, а с привязкой к архитектуре и внутренней реализации, переживу ;)

теперь по фактам

1. хочется максимально простого и понятного дизайна сущностей, незамутнённой развесистой клюквой иерархии, чтобы было много точек, куда легко вклиниться или надстроиться, но опять же чтобы не было мегатонн жёстко прописанных интерфейсов и делегатов. проблема из той-же темы, совместимость вызовов функций и интерфейсов от разных версий библиотек, и наоборот, необходимость эту совместимость поддерживать со стороны библиотеки.

2. хочется чтобы тоже самое можно было сделать с алгоритмами, как встраивать свою обработку, так и собирать новую логику из кусочков, включая смену обрабатываемых типов, не просто заанбокшенным типом из шаблона, а нечто более продвинутое. потому что сейчас – или клюква, или куча вклиненных делегатов, или копирование кусков кода. не спорю, бывают случаи, когда всё это можно оформить в элегантный дизайн. но как правило – или недостаток информации, или времени, а делать надо сейчас. хочется чтобы проблема решалась автоматом.

3. хочется, чтобы небольшая фичка из большой библиотеки не таскала за собой мегатонны всякого хлама, и даже если без хлама – чтобы без мегатонн хидеров, интерфейсов и т.п., чтобы вытаскивалось именно что нужно, а вызовы/зависимости как-то рассчитывались автоматом.

ко всем этим трём пунктам относится и вышеприведённый ранее пример художник.нарисовать(бумажка, инструмент, фигура). проблема бумажка-инструмент-фигура решилась новым (хардкоднутым!) слоем абстракции, а не решилась автоматом самой средой. хотя наверняка все три сущности неслабо подозревают о существовании друг друга, и все эти отношения можно вывести. и может быть даже слишком много знают друг о друге, уж слишком небольшая иерархия классов ;)

на всё это как-то намекают функциональные языки. но у них или жестокая головоломная парадигма, в которой императив на правах мутной примочки, или языки-наследователи машинных кодов, в которых функциональщина опять же на правах жалкой примочки, а не концепта.

по мне, кроме концепта объектов-сущностей, надо ещё что. во-первых, чтобы само описание программы как-то собиралось в исполняемое нечто в функциональном стиле. т.е. оно ближе к декларативно-размазанному, а не жёстко-хардкоднутому. во-вторых, чтобы можно было внутри этой программы организовать высокоуровневое взаимодействие в функциональном стиле. далее, на нижнем уровне логики мне более чем хватит императивного стиля, но и функциональщина пусть будет со всеми плюшками.

...
(Reply) (Parent) (Thread) (Expand)
[User Picture]From: retiredwizard
2013-10-24 11:52 am (UTC)
Не могу промолчать - просто гениально (ну на самом деле все проще: отображение моих мыслей :0))
ООП это полиморфизм. Ни один вменяемый человек его не использует (кроме библиотек, там это уместно!)

Унаследовать от _своего_ класса для прикладной задачи - скорее всего признак плохого дизайна.
Полиформизму не место в прикладных разработках.

А инкапсуляция и наследование от стандартных классов - это благо. Но это НЕ ООП.
(Reply) (Thread)