Конструктор объектно ориентированное программирование

Обновлено: 02.05.2024

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

Наследование в ООП . Конструкторы

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

Ниже ( main33.cpp ), представлена программа, демонстрирующая то, как можно работать с конструкторами. Можете попробовать самостоятельно в ней разобраться, а ниже я даю пояснение по ней, если возникло непонимание.

И так некоторые пояснение по программе main33.cpp:

  • У класса A есть три конструктора, как и положено они имеют имя, совпадающее с именем класса, но отличаются друг от друга параметрами. Это так называемая перегрузка конструкторов.
  • При создании объекта, запускается тот конструктор, который соответствует по параметрам. Т.е. когда мы создаем объект, на который указывает a , то запускается конструктор с одним параметром, когда создаем объект на который указывает a1 , то запускается конструктор без параметров.
  • А как третий конструктор в двумя параметрами, спросите вы. А вот это интересно. Обратите внимание на строку A::A():A(0, 0) . Она означает, что перед тем как запустится конструктор без параметров, будет запущен конструктор с двумя параметрами. На самом деле, другой конструктор можно запустить и внутри конструктора, просто написав A(20) в любом нужном месте.

В результате работы программы будут выведены следующие строки

Конструктор 2
0 0 Конструктор 3
Конструктор 1
25
50
Деструктор родителя
Деструктор родителя

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

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

Что такое конструктор

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

В прошлой статье, при рассмотрении парадигмы ООП, мы узнали, что такое классы и научились создавать экземпляры классов.


Давайте освежим знания и напишем простой класс на языке программирования PHP.

Пример простого класса:

Если выполнить программу, на мониторе вы увидите название модели автомобиля: car


Как я писал в предыдущей статье, в объектно-ориентированном программировании все является объектами.

Наш Car - пока еще не очень функциональный автомобиль. Он не умеет ездить, а знает только какая у него модель $model, цвет $color, максимальная скорость $maxSpeed и текущая скорость $currentSpeed.

Создадим еще один автомобиль:

Наш superCar ничем не отличается от обычного $car. И это очень плохо, так как нашему покупателю мы пообещали продать автомобиль с более высокими характеристиками, чем прошлый автомобиль.

Мы можем задать свойства автомобиля после создания экземпляра класса, но это будет выглядеть перед покупателем примерно так: Сейчас мы заменим двигатель, перекрасим, подождите пожалуйста часов 20, а еще лучше недельку. Думаю, что покупателю это не понравится и он уйдет из нашего автомобильного салона.

В объектно-ориентированном программировании принято инициализировать переменные при создании экземпляра класса. Для этого мы воспользуемся конструктором.

На языке программирования PHP пустой конструктор выглядит так:

Давайте встроим его в наш класс Car и создадим возможность присваивать переменным значения, при создании экземпляра класса:

А сейчас создадим сразу два автомобиля и зададим значения свойств при создании экземпляров классов:

Как вы видите, у обычного автомобиля (класс Car) мы задали модель = car, цвет = red, максимальную скорость = 100, текущую скорость = 0, а для supercar мы задали улучшенные характеристики при создании экземпляра класса.

Выводы

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

В следующих статьях мы разберем важные методы ООП программирования - геттеры и сеттеры, расскажем что такое public, static, private, protected.



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

Тем же, кто с парадигмами программирования незнаком, я рекомендую начать с краткого введения «Programming Paradigms – Paradigm Examples for Beginners».

Содержание

Введение в объектно-ориентированное программирование

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

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

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

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

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

Создание объектов — классы

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

Предположим, что нам доступно три вида персонажей, и мы хотим создать 6 разных, по 2 каждого вида.

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


Заметьте, что все персонажи имеют свойства name и species , а также содержат метод sayPhrase . Более того, у каждого вида есть метод, присущий только ему (например, у пришельцев это метод fly ).

Как видите, некоторые данные являются общими для всех персонажей, некоторые только для видов, а некоторые являются уникальными для каждого отдельного персонажа.

И такой подход работает. Мы можем без проблем обращаться к свойствам и методам подобным образом:


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

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

В качестве рефакторинга написанного выше кода мы можем создать класс для каждого вида персонажей:


Затем из этих классов можно инстанцировать наших персонажей таким образом:


После этого мы также можем обращаться к свойствам и методам каждого объекта:


Такой подход и само использование классов в целом хороши тем, что позволяют нам быстрее и безопаснее создавать объекты на основе этих самых «схем» в сравнении с «ручным» способом.

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

Что нужно помнить о классах

Согласно этому определению (англ.), выраженному более формально:

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

  • Помните, что классы не являются фактическими сущностями или объектами, а представляют схемы, которые мы используем для их создания.
  • По соглашению имена классов объявляются с первой заглавной буквы и прописываются в CamelCase. Ключевое слово class создаёт константу, исключая возможность её дальнейшего переопределения.
  • Классы всегда должны содержать метод-конструктор, который служит для будущего инстанцирования самого класса. В JS конструктор – это простая функция, возвращающая объект. Единственная особенность здесь в том, что при вызове с ключевым словом new она присваивает свой прототип в виде прототипа возвращаемого объекта.
  • Ключевое слово this указывает на сам класс и служит для определения его свойств внутри конструктора.
  • Методы можно добавлять простым определением имени функции и её исполняемого кода.
  • JS – это язык, основанный на прототипах, и внутри него классы используются только в качестве синтаксического сахара. Здесь это особой роли не играет, но лучше сей момент знать и учитывать. Более подробно эта тема раскрыта в статье «JavaScript prototypes and Inheritance – and Why They Say Everything in JS is an Object».

Четыре принципа ООП

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

Наследование

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

Разберём это на примере. Представьте, что все определённые нами выше персонажи будут врагами основного героя. И являясь врагами, они все будут иметь свойство power и метод attack .

Один из вариантов реализовать это – просто добавить указанные свойства и методы во все имеющиеся классы:


Но здесь мы сталкиваемся с повторением кода, чего желательно избегать. Более удачным решением будет объявить родительский класс Enemy , который затем будет расширен всеми видами врагов:


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

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

При инстанцировании новых объектов мы просто передаём параметры так, будто они объявлены в соответствующем конструкторе и… вуаля! Теперь мы можем обращаться к свойствам и методам, объявленным в родительском классе.


Далее предположим, что хотим добавить новый родительский класс, группирующий всех наших персонажей (независимо от того, враги они или нет), а также установить свойство speed и метод move . Сделать это можно так:


Сначала мы объявляем новый родительский класс Character . Затем расширяем его классом Enemy . И, наконец, добавляем новый параметр speed в функции constructor и super класса Alien .

Мы, как обычно, инстанцируем передачу параметров… и вот уже снова можем обращаться к свойствам и методам из «дедовского» класса.


Теперь, когда мы познакомились с наследованием, давайте отрефакторим наш код, чтобы максимально избежать повторения:


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

Что нужно помнить о наследовании

  • Класс может наследовать только от одного родителя. Расширять несколько классов нельзя, хотя для этого есть свои хитрости.
  • Вы можете безгранично увеличивать цепочку наследования, устанавливая родительский, «дедовский», «прадедовский» и так далее классы.
  • Если дочерний класс наследует какие-либо свойства от родительского, то он сначала должен присвоить эти свойства через вызов функции super() и лишь затем устанавливать свои.
  • При наследовании все родительские методы и свойства переходят к потомку. Здесь мы не можем выбирать, что именно наследовать (так же, как не можем выбирать достоинства или недостатки, получаемые нами от родителей при рождении. К теме выбора мы ещё вернёмся при рассмотрении композиции).
  • Дочерние классы могут переопределять родительские свойства и методы.


Предположим, что мы хотим изменить действие метода attack в классе Alien . Для этого его можно переопределить, объявив повторно таким образом:

Инкапсуляция

Инкапсуляция – это ещё один принцип ООП, который означает способность объекта «решать», какую информацию он будет раскрывать для внешнего мира, а какую нет. Реализуется этот принцип через публичные и закрытые свойства и методы.

В JS все свойства объектов и методы по умолчанию являются публичными. «Публичный» означает возможность доступа к свойству/методу объекта извне его тела:


Для большей наглядности давайте рассмотрим, как выглядят закрытые свойства и методы.

Допустим, нам нужно, чтобы класс Alien имел свойство birthYear и использовал его для выполнения метода howOld , но мы не хотим, чтобы это свойство было доступно вне самого объекта.

Реализовать это можно так:


После этого можно обращаться к методу howOld так:


Если же мы попробуем обратиться к нему напрямую, то получим ошибку. При этом закрытое свойство во время вывода объекта в консоль отображаться не будет.


Инкапсуляция полезна в случаях, когда нам требуются определённые свойства или методы исключительно для внутренних процессов объекта, и мы не хотим раскрывать их вовне. Наличие закрытых свойств/методов гарантирует, что мы «случайно» не раскроем эту информацию.

Абстракция

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

Этот принцип тесно связан с инкапсуляцией, поскольку мы можем использовать публичные и закрытые свойства/методы, определяя, что раскрывать, а что нет.

Полиморфизм

Осталось рассмотреть полиморфизм (звучит заумно, не так ли? В ООП самая крутая терминология…). Полиморфизм означает «множество форм», являясь, по сути, довольно простым принципом, отражающим способность метода возвращать разные значения, согласно определённым условиям.

Например, мы видели, что класс Enemy содержит метод sayPhrase . При этом все классы видов наследуют от класса Enemy , то есть у них тоже есть метод sayPhrase .

Но мы также видим, что этот метод при вызове для разных видов возвращает разные результаты:


И причина в том, что при инстанцировании мы передали каждому классу свой параметр. Это один вид полиморфизма – основанный на параметрах.

Другой его вид основывается на наследовании. Он относится к случаям, когда есть родительский класс, который устанавливает метод, впоследствии переопределяемый потомком этого класса.

Здесь также отлично подходит уже виденный нами ранее пример:


Эта реализация полиморфна, поскольку, если закомментировать метод attack в классе Alien , мы всё равно сможем вызвать его для объекта:


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

Композиция объектов

Композиция объектов – это техника, которая работает как альтернатива наследованию.

Когда мы говорили о наследовании, то упоминали, что дочерние классы всегда наследуют все родительские свойства и методы. Что ж, с помощью композиции можно присваивать свойства и методы объектам более гибким способом, в результате которого они получают только то, что нужно, и ничего лишнего.

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

Предположим, что хотим добавить способность полёта персонажам Bug . Как мы видели в коде, способность fly есть только у Alien . Значит, один из вариантов – это продублировать тот же метод в классе Bug :


Ещё один вариант – это переместить метод fly наверх в класс Enemy , чтобы его унаследовали и класс Alien , и класс Bug . Но так мы сделаем этот метод доступным для классов, которым он не нужен, например, Robot .


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

В нашем примере можно создать функцию, чьей единственной ответственностью будет добавление метода fly любому объекту, который она будет получать в качестве параметра:


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

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

Вот неплохое видео, в котором сравниваются наследование и композиция:

Обобщение

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

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

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

Конструкторы – это специальные встроенные методы классов. Как мы уже знаем, классы – это "чертежи" объектов , которые указывают, как нужно строить объект. То есть какие у этого объекта должны быть свойства и методы.

Когда транслятор языка видит строчку типа

var a = new Account();

Он понимает, что нужно создать новый объект из класса Account . Он обращается к описанию этого класса и создает такой объект, у которого есть все свойства и доступ к методам данного класса. Но что значит "создает"? До сих пор мы это не обсуждали. Объект как будто просто появляется и всё.

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

Эту ссылку возвращает нам метод-конструктор , который работает "под капотом" и скрыт от нас. Он уже заранее написан разработчиками языка.

Работает и работает, ну и ладно. Нужно ли нам как-то использовать конструкторы?

Во-первых, как это сделать, если конструктор от нас скрыт?

Классы дают нам возможность добавить свой собственный конструктор. В разных языках это делается по-разному. Например, вот тут

конструктором является метод, у которого имя Account совпадает с именем класса – именно так транслятор понимает, что это конструктор. А вот тут:

конструктором является метод со специальным "магическим" именем __construct() .

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

Если у нас теперь есть собственный конструктор, то кто будет заниматься собственно созданием объекта? Должны ли мы теперь это делать сами? Нет, не должны.

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

Вызов "родительского" конструктора происходит через специальные инструкции. Это тоже зависит от конкретного языка. Такой инструкцией может быть super() :

или parent :

Главный смысл тут в том, что если вы в своем конструкторе не вызовете родительский конструктор, то объект не будет правильно создан. Также обратите внимание, что если вы отнаследовали класс от какого-то другого класса, и в каждом есть ваш собственный конструктор, то каждый из этих классов должен вызывать в своем конструкторе super() (условно).

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

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

Например, вам нужно создать банковский счет, и сразу положить на счет 100 долларов. Вы могли бы написать так:

var a = new Account();
a.currency = 'USD';
a.sum = 100;

Вы можете сделать такой конструктор, который сразу будет устанавливать сумму и валюту счета при его создании:

Теперь вы можете создавать счета вот так, просто передавая параметры в конструктор:

var a = new Account('USD', 100);

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

Все ссылки на статьи и ролики моего канала Old Programmer :
Программирование. Тематическое оглавление моего Zen-канала (Old Programmer) . Все ссылки на материалы об объектно-ориентированном программировании собраны в один раздел .

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

Объектно-ориентированное программирование (C++). Статья 1 (динамические и статические объекты, три кита ООП)

Конструкторы и деструкторы в ООП

Зачем нам эти методы? - спросите вы. А вот зачем. В конструктор можно поместить код, который инициализирует некоторые переменные класса, выделяет им место в памяти (если нужно), присваивает определенные значения. Типичным примером может быть объект, который описывает окно. Нужно задать размер окна, цвет, заголовок, другие его свойства. Задать элементы в окне. Метод деструктор служит для того, чтобы, например, освободить память, если она выделялась в процессе выполнения конструктора или других методов, ну и, возможно, произвести другие изменения, которые важно сделать именно после уничтожения объекта. Конструктор и деструктор делают объект самодостаточной единицей программирования.

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

Пример использования конструктора и деструктора в языке C++

Программа ниже как раз и демонстрирует работу конструктора A(int, int) и деструктора ~A() . Мы видим, что при создании объекта динамически создаются две переменные типа int и им присваиваются значения, переданные через параметры конструктора. При уничтожении объекта ( delete ) автоматически выполняется деструктор, где освобождается память, которая была выделена в конструкторе. В примере ниже у объекта есть также метод mul , который возвращает произведение двух переменных класса, значения которых задаются при создании объекта.

Вот, на сегодня и все, я надеюсь вы продвигаетесь в своем стремлении стать программистами. Читайте мои статьи на канале Old Programmer и ставьте лайки. Подписывайтесь на мой канал. Пока!

Читайте также: