Тостер, ну почему ты не сохраняешь где-нибудь в local storage сообщения, которые не дописаны? Ааааа! В общем, написал, закрыл вкладку, пишу снова.
Вопрос довольно широкий и ответ на него можно найти в разных книгах, которые здесь советовали начинающим программистам в каждом втором топике, но я попробую рассказать просто и понятно, хотя и не гуру. Тут главное понять суть, языки ни при чем.
Понятие алгоритма (машина Тьюринга) императивно. Первые архитектуры компьютера, описывающие как хранить код и данные (архитектура фон Неймана и т.п.), тоже императивные. Императивно/дискретно работает "физический мир полупроводниковой логики". Императивная парадигма является "родной" для компьютера. А ООП - это продолжение, эволюция императивной парадигмы.
Императивная программа состоит из инструкций, которые что-то меняют в памяти. Если смотреть с минимальным зумом, то много инструкций - это функция. А много функций - это модуль. С точки зрения инженерии ПО важно программе быть максимально модульной, максимально DRY, легко изменяемой, эффективно решающей задачи поставленные и еще много чего. Так вот, кажется, у нас все есть, растащил по директориям кучу файлов, функции/модули/область видимости, но этого не хватит, ведь есть же еще типы, а их надо передавать туда-сюда, обрабатывать. Есть алгоритмы, которым глубоко фиолетово, что обрабатывать, будь то массив, словарь, список, дерево или даже
генератор (люблю питон @ пиарю где могу). Кстати говоря, тут можно задуматься о сути итераторов, но это оффтопик.
Во времена популярности ассемблера типов "еще не было". И это вызывало проблемы: множество рутины, множество кода, возможность прострелить себе регистр и так далее. Во времена C были примитивные типы и даже структуры, которые могли хранить данные и указатели на данные. Но все еще как-то неудобно, множество низкоуровневых действий. Нет даже строк, представлять их в виде массива - не слишком удобно, гораздо лучше иметь что-то вроде черного ящика. Где-то в те же времена уже было ясно, что нужно иметь еще более абстрактные типы, которые могут хранить контекст, иметь свои функции (методы), инициализироваться, деинициализироваться. В общем, нужно создавать типы "высокой абстракции", а еще уметь их модифицировать, расширять, комбинировать. И лучше еще с памятью явно не работать. А еще чуть позже популяризовались языки с динамической типизацией, где стало можно не заботиться о типах совсем. Мне даже кажется, что тут уместно сказать, что типов будто "уже не стало". Все есть контейнеры со своей областью видимости. Правда, все это медленно, особенно, если учесть, что динамическая типизация очень любит интерпретацию или динамическую компиляцию. Чем выше уровень, тем все тормознее.
На самом деле все эти идеи были очень давно (ООП, динамическая типизация), еще даже раньше, чем появился C, однако людям было нужно время, чтобы осознать, обкатать.
С ООП теперь все удобно. Кроме модулей и функций есть сложные типы (классы), которые можно свободно создавать, наследовать, регулировать область видимости, заменять методы, использовать интерфейсы, "строить модули внутри модулей", не использовать глобальные переменные, внедрять зависимости, не дублировать код. В языках, реализующих неклассическое ООП, существует еще больше свободы. Можно использовать миксины направо-налево, классы являются объектами, а где-то и классов нет, потому что есть объект и с ним и так все хорошо))
Это все особо важно и нужно в прикладных задачах, где обрабатываются разные данные, выводятся в разных видах или где есть интерфейс пользователя с множеством однотипных элементов. Практически всегда там возникает ситуация, когда нужно определить множество похожих объектов, сделать удобное API, использовать и расширять стороннюю библиотеку. Кроме того, существует множество паттернов, которые можно переносить откуда угодно куда угодно, не думая при этом. В вебе, с учетом того, что 95% приложений крутятся вокруг MVC и его вариаций, ООП очень даже кстати: модельки - это однотипные объекты из однотипных полей, имеющие в себе какую-то логику (свои функции/методы), контроллеры - однотипные объекты (получение данных/параметров запроса, выдача необходимого ответа в нужном виде). Если взять любой фреймворк, то там реализовано множество модулей, которые что-то реализуют: ORM, роутинг, кэш, сессии, request-объекты, response-объекты, какие-нибудь карты сайта, админки, модельки для юзеров, что-нибудь для работы со статикой, формы, локализация... Если нужно что-то расширить, то достаточно посмотреть на API, отнаследоваться, реализовать/заменить метод.
Наверное, нужны какие-то примеры. Вот представьте, что пишете простой сайт, который должен отдавать готовый HTML. Но кроме этого сайт модный, поэтому он полностью ajax (html5 history api), никаких перезагрузок, только первая загрузка страницы, которая выгружает и фронт и стили и отрендеренную страничку, а потом дергаются нужные данные для других страниц (в json, туда кладется отрендеренная часть и еще какие-нибудь важные данные) с бэкенда. Оговорюсь, что это не SPA, а именно full ajax сайт, который все-таки еще рендерится на сервере в каком-нибудь серверном фреймворке. И ваш бэкенд должен отдавать, соответственно, 2 варианта страниц: обычную (как при прямом заходе по ссылке, это и для пользователя и для бота) и json-вариант для других страниц (это уже только для пользователя, соответственно). Вы создаете крутой контроллер, который в зависимости от параметров в GET, либо в зависимости от заголовка рендерит то json, то html. А дальше наследуетесь от этого контроллера и совсем не заботитесь о том, что у вас отдает конкретный контроллер: переопределяете метод с контекстом и отдаете в нем нужные данные, а уж что с ними станет в шаблоне или как их обработает фронтенд, получив из json, - без разницы. Нужно сделать только ajax-контроллер? У вас для него есть класс, осталось опять же только отнаследоваться. Нужно отдавать SEO-заголовки? Можно сделать миксин для контроллера и миксин для модельки и снова переопределить только один метод а-ля
get_meta_title
и все. Ну, понятное дело, я тут упустил мелкие подробности и все это актуально больше для языка вроде python и подобных (уж на чем пишу), но вот пример ООП-задач.
Или у вас есть либа... Допустим, она зависит еще от трех других либ, плюс предоставляет какое-то API. Выглядит в виде класса, в нем несколько методов, пара публичных, + конструктор, + свойства. Пишете тесты, подменили зависимости объектами-пустышками и все получилось красиво. А как это сделать, использовать ли там какие-то специальные
контейнеры или прямо на лету пропатчили класс (̶̶к̶а̶к̶,̶ ̶н̶а̶п̶р̶и̶м̶е̶р̶,̶ ̶м̶о̶ж̶е̶т̶ ̶п̶и̶т̶о̶н̶,̶ ̶п̶о̶т̶о̶м̶у̶ ̶ч̶т̶о̶ ̶о̶н̶ ̶в̶а̶м̶ ̶т̶а̶м̶ ̶н̶е̶ ̶"̶п̶а̶б̶л̶и̶к̶ ̶с̶т̶а̶т̶и̶к̶ ̶в̶о̶й̶д̶ ̶в̶с̶е̶ ̶м̶н̶о̶г̶о̶с̶л̶о̶в̶н̶о̶е̶ ̶и̶ ̶т̶о̶л̶с̶т̶о̶е̶ ̶г̶д̶е̶ ̶м̶и̶н̶и̶м̶а̶л̶и̶з̶м̶"̶), не суть.
Да вот даже возвращаясь к тем итераторам, что я выше затронул. Допустим, есть у вас итератор и нужно сделать его thread safe. То есть при вызове метода, который возвращает следующий элемент, вы должны захватить мьютекс, а потом его отдать. Это легко можно сделать с помощью наследования: просто наследовавшись и обернув нужные методы. Либо с помощью интерфейсов, чтобы было универсально и для каких угодно объектов: реализуется интерфейс итератора в виде класса, который принимает другой итератор и его оборачивает.
Вообще, примеров очень много, они повсюду, особенно, в вебе. Если в таких прикладных задачах не использовать ООП, то я даже не представляю, как тестировать этот код. Как его поддерживать, меняя логику? А как менять компоненты? Как быть DRY? Тут я, кстати, вспомнил про то, что вот, например, есть в C структуры. Это что-то вроде урезанного класса и на самом деле можно реализовать многие штуки с ними, писать отличный код, но он просто будет сложнее в поддержке и по скорости разработки будет очень далеко позади. Ядро линухи выглядит очень красиво и совсем там не лапша. Однажды фиксил драйвер, чтобы заработал 5ггц вай-фай, так вот он был красив и строг внутри. Кстати, в Go классы реализованы в виде структур, но я не смогу подробнее рассказать об этом, т.к. не смотрел этот язык. ООП - это не какая-то супер фича, а это всего лишь подход. Языки же добавляют синтаксический сахар, реализуют классическое ООП (java/c#/c++/etc) или же неклассическое (python, js, etc).
Кроме ООП-парадигмы и процедурной парадигмы есть еще, например,
функциональная. Но это отдельная совсем тема. В мультипарадигменных языках можно писать по-разному. Если что-то удобно представляется в виде цепочки функций, выглядит так, как будто нет побочного эффекта или его легко изолировать, то нет стоит городить классы или процедурный ад. Тем более в языках, которые близки к ФП, обычно полно встроенных функций, можно объявлять анонимные функции, почему бы этим не пользоваться. Ну или, например, у вас получился какой-то модуль, в котором не нужна ни инициализация, ни контекст ему не нужен, все выглядит как просто 4 связанные функции, то зачем здесь нужно ООП? Хотя если что-то из этого можно кастомизировать, а вы чувствуете, что это понадобится, т.к. есть объекты, которые будут расширять функциональность, то стоит задуматься. Главное не
оверинжинирить.