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


Кратко и просто про отношения между классами, которые видны на схемах
Наследование — это механизм в объектно-ориентированном программировании, при котором один класс (дочерний) может заимствовать свойства и методы другого класса (родительского). Это позволяет избежать дублирования кода, вынося общие черты в базовый класс. Расширять или изменять функциональность родительского класса в дочерних. Композиция — это принцип проектирования, при котором один класс содержит объекты другого класса как свои атрибуты. Это означает, что объект одного класса «состоит из» или «использует» объекты других классов для выполнения своей работы. Пример с «ГужеваяПовозка» и «ТягловаяСила» — отличная иллюстрация композиции: Класс «ГужеваяПовозка» содержит атрибут «тягловаяСила», который является объектом класса «ТягловаяСила».Повозка использует методы объекта «тягловаяСила» для выполнения своих задач (например, запрячь() или распрячь()).Классы «Лошадь» и «Вол» реализуют интерфейс «ТягловаяСила», предоставляя конкретные реализации методов. Про интерфейс, его реализацию, абстрактные классы расскажу в следующих постах. Постараюсь это сделать простым и понятным языком. В данном случае если в двух словах, благодаря интерфейсу «ТягловаяСила» компилятор знает, что есть методы запрячь() и распрячь(), но в классах, которые этот интерфейс реализуют, имеется конкретная реализация этих методов. И она может быть разная в каждом из классов. Таким образом, можно легко заменить вола на лошадь или поправить метод, например, запрячь(), при этом не переписывая лавину кода. Почему композиция лучше, чем наследование в данном случае? Гибкость: если бы Лошадь и Вол наследовались от ГужеваяПовозка, это создало бы жесткую связь между классами, что усложнило бы добавление новых типов тягловой силы. Композиция позволяет динамически менять поведение объекта, просто меняя его составляющие части. Отделение ответственности: класс «ГужеваяПовозка» отвечает только за работу с повозкой, а классы «Лошадь» и «Вол» — за работу с тягловой силой. Это делает код более модульным и легким для понимания. Соблюдение принципов ООП: принцип единственной ответственности: каждый класс имеет одну четко определенную задачу. Принцип открытости/закрытости: можно добавлять новые типы тягловой силы без изменения существующих классов. Аналогия из реальной жизни. Автомобиль содержит двигатель, колеса, руль и другие компоненты. Но автомобиль не является подклассом двигателя или колеса. Вместо этого он использует эти компоненты для выполнения своей работы. Точно так же ГужеваяПовозка использует объекты типа ТягловаяСила для выполнения своей задачи.
Сущности класса, если не вдаваться в детали — это поля, данные и методы. Поля — это свойства класса, описывающие его состояние. Методы класса — это функции, которые определяют поведение класса и работаю с его данными и полями. У класса есть конструктор и деструктор — это специальные методы класса, которые создают и уничтожают объект соответственно.
Именно на основе класса по шаблону и создается объект.
Немного технических деталей создания объекта
Создает объект конструктор класса — он запрашивает у операционной системы блок памяти размером достаточным, чтобы в него поместить объект и инициализирует этот блок памяти начальными значениями и возвращает созданный объект программе. В контексте языка C++ конструктор возвращает ссылку на объект — на адрес, с которого начинается блок памяти с объектом. В результате создания объекта в памяти образуется массив байт. И что именно за сущности в этом массиве — понятно благодаря классу, который этот объект описывает.
Деструктор уничтожает объект, освобождает занимаемую им память. Операционная система помечает эту область памяти, как свободную.
Создание и уничтожение объекта
Созданный объект должен быть уничтожен, иначе образуются утечки памяти. Во многих современных языках программирования деструктор не нужен. Созданный объект уничтожится сборщиком мусора, как только объект перестанет быть нужным. Но в некоторых языках программирования сборщика мусора нет и тогда удаление объекта — ответственность разработчика. Можно описать, например класс, для хранения объекта которого нужно много памяти. И в бесконечном цикле вызывать конструктор для создания объектов данного класса. Тогда в диспетчере задач хорошо будет видно, как запущенное приложение занимает все больше и больше памяти.
Наследование позволяет создавать новые классы на основе существующих, чтобы повторно использовать код, добавлять или изменять поведение, добавлять новые поля. Классы-наследники — это как талантливые дети, которые умеют больше, чем их родители.
Некоторые языки программирования позволяют наследоваться от нескольких классов, а некоторые только от одного. Некоторое подобие множественного наследования можно сделать через реализацию интерфейсов.
В контексте примера с пресс-формой можно представить базовый класс: пресс-форма для штамповки кастрюли, а класс-наследник -модифицированная пресс-форма, которая штампует кастрюли с утолщенным дном.
Инкапсуляция защищает данные класса, не позволяет извне эти данные менять, скрывает их и позволяет с ними работать через определенный интерфейс. Тем самым сохраняется состояние класса и в целом — стабильность приложения. Скрытые данные можно изменить только в самом классе, которому они принадлежат, поэтому в случае поиска ошибки в коде, которая возникла из-за изменения состояния поиск сводится к поиску ошибки в методах данного класса, которые работают с полями класса. Без инкапсуляции данные класса невозможно защитить от изменений и ошибка может возникнуть где угодно. И на ее поиски может уйти очень много времени. Извне со скрытыми данными можно работать через интерфейс, который предоставляет класс. Обычно — это методы, которые возвращают значение поля (геттеры) и устанавливают значение поля (сеттеры).
В контексте пресс-формы инкапсуляция — это когда никому неинтересно, как устроена пресс-форма. Что там внутри — без разницы. Главное, чтобы оператор смог с этой пресс-формой легко взаимодействовать и наштамповать детали.
Полиморфизм позволяет работать универсально с объектами разных типов. Простыми словами — это, например в базовом классе есть метод, который работает с массивом целых чисел, а в классе-наследнике есть метод с точно таким же именем, но работает он с массивом дробных чисел. В другом наследнике метод с таким же именем работает с массивом строк. Полиморфизм идет рядом с наследованием.
В контексте пресс-формы — это примерно как одна пресс-форма сделана из очень твердого материала и может штамповать детали из одних материалов. А есть точно такая-же пресс-форма, но она попроще и позволяет штамповать детали из менее твердых материалов. И перепутать эти прессформы нельзя!
Абстракция — это выделение главного, важного для решения задачи, и скрытие лишних деталей реализации. Абстракция в контексте пресс-формы — это выделение главного функционального назначения пресс-формы и отвлечение от сложных технических деталей её конструкции.
Например, нужна пресс-форма, которая может принимать расплавленный пластик и формировать из него крышку от бутылки за 10 секунд, с точностью 0.01 мм и не важно, как именно внутри сделаны каналы охлаждения, как устроены вставки, как крепится форма к станку — это детали реализации, которые не нужны на данном уровне абстракции.
В будущих статьях попробую простым языком рассказать про основы программирования в стиле ООП.