Примечание: более новая версия этого текста доступна по адресу https://dev.to/nuoji/to-oo-or-not-to-oo-5dm5

Отчасти из-за работы Джонатана Блоу над Jai и очевидного отсутствия объектно-ориентированного программирования в языке, я начал задумываться о том, является ли объектно-ориентированный объект хорошей вещью или нет. Я слушал - и читал - различные критические замечания в адрес OO и немного размышлял над проблемой. После серии Crafting Interpreters Боба Нистрома, в которой вторая часть написана на очень ясном и легком для понимания процедурном языке C, также было хорошим напоминанием о том, на что раньше было похоже программирование.

До OO было просто писать код

Я начал программировать в 1982 году. Мне было 9 лет, и программирование на домашних компьютерах почти всегда было BASIC (или ассемблером, если вы хотели немного увеличить скорость). Это была эпоха первых «домашних компьютеров». Компьютерные журналы печатали списки игр и приложений. Это было отчасти «получение программ по дешевке» и отчасти «обучение программированию».

BASIC - это не совсем структурированное программирование, по крайней мере, не те ранние версии. «Структурированное программирование» ограничивалось такими операторами, как GOSUB 1340. Тем не менее, вы определенно можете построить что-нибудь с ним. Все игры и приложения написаны на БЕЙСИКЕ. Ограничением обычно была память машины (обычно 16 КБ), а не структура. Возможно, это был не изящный код, но мы справились.

Несмотря на то, что более поздние итерации BASIC улучшили бы большую часть 8-битных реализаций, работа на Паскале казалась прекрасной. Это было намного мощнее, не говоря уже о том, насколько проще было писать структурированный код. Даже в этом случае дизайн мало чем отличался от написания ассемблера или BASIC. Вы просто начали с одного конца и строили вещи, пока они не были сделаны. Было действительно легко выполнить работу.

В конце концов я немного выучил C ++, но объектно-ориентированная часть языка по большей части ускользнула от меня. Все изменилось только после того, как я перешел на Java - и в то время я считал, что это к лучшему.

Java и «настоящий» OO

Я говорил людям, что не разбирался в объектно-ориентированном программировании, пока не изучил Java. Возможно, это не совсем так, но это правда, что я не пробовал заниматься каким-либо «объектно-ориентированным дизайном», пока не изучил Java.

Java действительно заставляла вас делать объекты. Лично я начал, когда апплеты были новой популярностью, а язык все еще находился в версии 1.0.2. Это было круто и волшебно. Объекты, на которых вы можете создавать эти маленькие единицы, чтобы делать что-то, почти как программирование маленьких роботов. Вместо простого программирования вы сделали этот дизайн, в котором независимые объекты разговаривают и производят результат. Это было потрясающе. И конечно, это было неправдой.

Неожиданно появились статьи и книги о том, как выполнять настоящий объектно-ориентированный объект. Казалось, дело в том, что нужно отбросить большую часть процедурного программирования. Предполагалось, что это было плохо, так же как неструктурированное программирование было процедурным программированием. Чем больше мы думали об объектах, тем лучше было. Было ясно, что нирвана близка - она ​​доступна для тех счастливчиков, которые могут зарабатывать на жизнь программой Java.

Кошмары объектно-ориентированного моделирования

В свободное время я работал над следующей версией сложной онлайн-игры, которая изначально была написана как BBS door (онлайн-игры BBS, когда мы использовали модемы с коммутируемым доступом). Первоначальная версия была написана на QBasic с переписанным на Turbo Pascal примерно 80% игры.

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

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

Делать настоящие вещи

Тем временем я начал профессионально работать программистом. Писал на Perl, Java, изучал Objective-C и Ruby. С Objective-C я обнаружил, что объектно-ориентированный подход Java / C ++ был всего лишь одним брендом объектно-ориентированного программирования. В Java идея заключалась в создании крошечных классов, которые были собраны в единое целое, но в ObjC объекты вместо этого использовались как высокоуровневый «клей» между более крупными компонентами, внутренне написанными в прямом процедурном коде C. Мелкозернистые классы Java будут считаться полной противоположностью хорошего дизайна для Objective-C. Итак, если объектно-ориентированный объект должен относиться к процедурному программированию, а процедурный - к неструктурированному - почему не было даже единого мнения о том, как выполнять «правильное объектно-ориентированное программирование»?

Мой любимый проект все еще провалился. Я все еще пытался моделировать вещи в стиле Java, и у меня ничего не получалось. Но потом я попытался написать это на Ruby, и случилось кое-что неожиданное.

Делать дела

В Java легко увязнуть в написании классов. Java сравнительно многословен, поэтому просто написание кучи классов, написание геттеров и сеттеров кажется, что вы проделали довольно много работы. Вот почему было легко просто тратить время на тестирование моделей и при этом чувствовать, что вы чего-то добиваетесь.

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

Внезапно я больше не мог себя обмануть. После реализации части модели в Ruby внезапно стало ясно, что я не добавляю ничего ценного, создавая эти классы игровой модели. Это была работа, но не настоящая. Мне нужна была новая идея.

В то время я профессионально работал над покерными серверами, и было ясно, что экземпляр покерной игры представляет собой просто структуру данных с колодой, текущими ставками и игроками. Действия игрока были просто командами, действующими на эти данные по определенным правилам. Может, эта идея сработает…?

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

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

Я решил свою большую проблему OO… приняв более процедурный образ мышления.

Еще не совсем там

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

В профессиональном плане, хотя я мог делать все «причудливые» объектно-ориентированные решения с отражением, полиморфизмом и т. Д., Мой собственный стиль объектно-ориентированного программирования имел тенденцию отдавать предпочтение простым, явным и очевидным решениям. Но я выбирал решения, потому что мой опыт показал мне, что они лучшие, а не потому, что я понимал, что есть какие-то проблемы с ОО.

Новая проблема

Я построил свой игровой сервер, и все было в порядке. Было тривиально расширять и очень просто добавлять новые функции. Была только одна проблема: клиент.

Раньше я писал о клиентах, но на меньшем. Пока у вас не более 10–15 экранов, вы можете обойтись большинством дизайнов. В моем игровом клиенте было более 50 различных диалоговых экранов и множество различных состояний. Вещи становились беспорядочными.

У меня была модель, представления и контроллеры, и я все еще чувствовал, что у меня нет контроля везде было так много состояний. Потому что это суть объектно-ориентированного программирования в стиле Java / C ++: разделить состояние на маленькие части и позволить каждому объекту управлять своей определенной частью состояния приложения. Это действительно очень плохая идея, поскольку сложность примерно соответствует квадрату различных взаимодействующих состояний. Кроме того, очень заманчиво просто позволить состоянию быть неявным в некоторых (комбинациях) значений переменных-членов. «Зачем нужно явное состояние, если вы можете проверить значение переменной, чтобы выяснить это?»

Заключение

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

Это не значит, что использование объектов обязательно плохо. С одной стороны, это очень мощный инструмент для создания иерархий рендеринга пользовательского интерфейса, а пространство имен вместе с цепочкой могут создать очень плавный и читаемый код, сравните: urlescape (substr (string, 0, strlen ( string) -2) в string.substr (0, -2) .urlescape (). Однако объект моделирование с объектами, которые сохраняют состояние или воздействовать на другие объекты - здесь кажется, что объектно-ориентированный объект пошел не так.

Существует также (в основном забытый) стиль объектно-ориентированного программирования Objective-C, который оказывается даже лучше для создания графических интерфейсов пользователя, чем Java / C ++, поскольку поздняя привязка диспетчеризации и среды выполнения значительно приближает его к языку сценариев. К сожалению, Apple, бывшие чемпионы Objective-C, в значительной степени забыли, в чем на самом деле была идея ObjC, и теперь заменяют ее Swift, который принимает объектно-ориентированный стиль Java / C ++.

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

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