Найти в Дзене
Блокнот математика

Что такое ООП

Объектно-ориентированное программирование. Про другие варианты расшифровки говорить принципиально не хочу. Итак, в чем же там дело и почему столько разговоров было (и есть) на эту тему? ООП — это парадигма программирования. Я писал немного об эволюции: от прямой работы с командами процессора до полного отвлечения от "железа". ООП — это еще один шаг в сторону абстракции. Давайте для примера предположим, что нам надо работать с такими объектами, как матрицы, векторы, тензоры и проч. Если язык позволяет, можно их описывать как многомерные массивы. Если нет, то просто массивы тоже годятся, только надо договориться, как два индекса превращаются в один. Потом вы можете создавать разные процедуры (функции) для работы с этими объектами, но придется тщательно комментировать свои намерения. Можно пойти дальше, и создать структуру (запись, хеш, словарь...), то есть составной тип данных, содержащий внутри себя несколько переменных и/или массивов разного типа. Это позволит описать матрицу, например

Объектно-ориентированное программирование. Про другие варианты расшифровки говорить принципиально не хочу.

Итак, в чем же там дело и почему столько разговоров было (и есть) на эту тему?

Юмор. https://habrastorage.org/webt/gd/9f/ad/gd9fadtyygideailxszfgw2cs_k.png
Юмор. https://habrastorage.org/webt/gd/9f/ad/gd9fadtyygideailxszfgw2cs_k.png

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

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

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

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

Более того. Считается правильным закрывать данные в объекте от доступа извне. Доступ к ним возможен только через "методы": те самые встроенные в объект процедуры. Это дает уже некоторую пользу помимо "просто удобства": например, если элементы матрицы нельзя изменить иначе, чем через интерфейс, то матрица "знает", что ее элементы не менялись. Вычислив один раз определитель, она может его сохранить внутри себя и повторно не вычислять. Он же не менялся.

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

Иногда говорят, что с объектом можно "общаться", посылая ему "сообщения", а он соответственно реагирует.

Всё это называется "инкапсуляция".

Классом обычно называется тип, структура данных, а объектом — переменная этого типа.

Создав таких классов: вектор, матрица, тензор — мы можем в каждом предусмотреть метод "умножить", и это всё будут разные умножения.

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

Пример из реальной жизни: разные ведомства по сути — инкапсулированы. Мы не знаем, что у них внутри, и их структура может меняться. Но мы знаем "интерфейс": можем посылать сообщения, и ведомства могут друг другу их посылать. И нас не волнует фамилия конкретного чиновника, выдающего нам ту или иную справку или принимающего на почте посылку, и еще менее интересует фамилия его начальника и внутренняя структура заведения.

Далее. Очень часто разные объекты похожи. Например, создав класс для матриц, мы можем создать классы для симметричных, ортогональных и положительно определенных матриц, в которых что-то иначе, что-то проще, что-то новое, но многое можно не писать заново. Например, сложение.

Предполагается, что есть механизм "наследования", при котором можно произвести класс от уже имеющегося, унаследовав его методы. Что-то добавим, что-то изменим, но многое просто придет без изменений.

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

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

Наследование может быть или не быть множественным. Если оно не множественное, то у класса есть один предок, или нет предков (в некоторых языках это есть, в некоторых все классы предки одного какого-нибудь первокласса). Если множественное, то у класса может быть несколько предков. Это не так просто, как кажется, но иногда бывает естественно. Например, класс "симметричная положительно определенная матрица" естественно унаследовать от симметричных и положительно определенных. Естественно-то да, но головной боли может быть много. Например, и те, и другие могут ычислять определитель, причем по-разному, и не всегда понятно, какой метод наследуется.

Здесь путаница в терминах. Помимо "наследования", есть еще "расширение", хотя по сути это может быть сужение класса. Например, если мы от "вообще" матриц произвели квадратные, а от квадратных — симметричные. Но, сужая класс, мы расширяем функциональность: вообще матрицы можно складывать, а умножать (одного размера) не всегда; а квадратные можно умножать, и еще определитель у них есть. И так далее.

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

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

Будет ли эта функция работать на наследнике класса? Например, если матрица "симметричная"? Формально это другой тип и мало ли что вы там изменили. Полиморфизм же предписывает приводить тип и справляться с ситуацией.

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

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

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

ООП при правильном использовании позволяет делать сложные вещи очень просто. Классический пример — это библиотеки классов для оконного интерфейса. Бесчисленные кнопки, окна, линейки и меню все связаны отношением наследования в дерево классов. Абстрактное "первоокно" имеет размеры и положение и умеет лишь себя рисовать и стирать, и то это декларировано только. Затем можно реализовать "перетаскивание" как последовательное стирание-рисование с изменением положения, и это будет полиморфно работать на всех наследниках класса! Сделав окно, вы можете унаследовать от него диалоговое окно, и так далее.

Есть полностью объектные языки, в которых всё — даже константы 42 и "сорок два" — суть объекты и у них есть методы. Есть языки, в которых ООП осознанно нет. Во многих эти средства есть, но они не являются обязательными.

Минус ООП в том, что оно очень усложняет язык. Язык Си легко умещается на лист А4. Язык С++ так просто не изложишь: множество конструкций, концепций, нюансов и подводных камней.

Странно то, что ООП из парадигмы программирование превратилось в фетиш. Священные войны "какой язык истинно объектный", и простителен ли грех нарушения священных заповедей ООП, длятся до сих пор.