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

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

Теперь давайте погрузимся в эти концепции и руководства по ООП!

Вот что будет рассмотрено:

  • Что такое объектно-ориентированное программирование?
  • Строительные блоки ООП
  • Четыре принципа ООП
  • Что изучать дальше

Что такое объектно-ориентированное программирование?

Объектно-ориентированное программирование (ООП) — это парадигма программирования в информатике, основанная на концепции классов и объектов. Он используется для структурирования программы в простые повторно используемые фрагменты кода (обычно называемые классами), которые используются для создания отдельных экземпляров объектов. Существует множество объектно-ориентированных языков программирования, включая JavaScript, C++, Java и Python.

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

Класс – это абстрактный план, который создает более конкретные и конкретные объекты. Классы часто представляют широкие категории, такие как Car или Dog, которые имеют общие атрибуты. Эти классы определяют, какие атрибуты будет иметь экземпляр этого типа, например color, но не значение этих атрибутов для конкретного объекта.

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

Например, наш класс Car может иметь метод repaint, который изменяет атрибут color нашего автомобиля.
Эта функция полезна только для объектов типа Car, поэтому мы объявляем ее в классе Car, что делает ее метод.

Шаблоны классов используются в качестве схемы для создания отдельных объектов. Они представляют собой конкретные примеры абстрактного класса, такие как myCar или goldenRetriever. Каждый объект может иметь уникальные значения свойств, определенных в классе.

Например, мы создали класс Car, содержащий все свойства, которыми должен обладать автомобиль, color, brand и model. Затем мы создаем экземпляр объекта типа Car, myCar для представления моей конкретной машины.

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

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

Преимущества ООП для разработки программного обеспечения

  • ООП моделирует сложные вещи как воспроизводимые простые структуры.
  • Многоразовые объекты ООП могут использоваться в разных программах.
  • Полиморфизм допускает специфичное для класса поведение
  • Легче отлаживать, классы часто содержат всю применимую к ним информацию.
  • Надежно защищает конфиденциальную информацию посредством инкапсуляции

Как структурировать программы ООП

Давайте возьмем реальную проблему и концептуально спроектируем программу ООП.

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

Как бы вы разработали простое многоразовое программное обеспечение для моделирования собак?

С сотнями собак было бы неэффективно писать уникальные записи для каждой собаки, потому что вы написали бы много избыточного кода. Ниже мы видим, как это может выглядеть с объектами rufus и fluffy.

//Object of one individual dog
var rufus = {
    name: "Rufus",
    birthday: "2/1/2017",
    age: function() {
        return Date.now() - this.birthday;
    },
    attendance: 0
}

//Object of second individual dog
var fluffy = {
    name: "Fluffy",
    birthday: "1/12/2019",
    age: function() {
        return Date.now() - this.birthday;
    },
    attendance: 0
}

Как вы можете видеть выше, между обоими объектами много дублированного кода. Функция age() появляется в каждом объекте. Поскольку нам нужна одна и та же информация для каждой собаки, вместо этого мы можем использовать объекты и классы.

Группирование связанной информации для формирования структуры класса делает код короче и упрощает его обслуживание.

В примере с собачьим присмотром программист может подумать об организации ООП:

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

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

Класс Dog — это общий шаблон, содержащий в качестве атрибутов только структуру данных и поведение, общее для всех собак.

Затем мы создаем два дочерних класса Dog, HerdingDog и TrackingDog. Они имеют унаследованное поведение от Dog (bark()), но также и поведение, уникальное для собак этого подтипа.

Наконец, мы создаем объекты типа HerdingDog для представления отдельных собак Fluffy и Maisel.

Мы также можем создавать такие объекты, как Rufus, которые подходят под широкий класс Dog, но не подходят ни под HerdingDog, ни под TrackingDog.

Строительные блоки ООП

Далее мы более подробно рассмотрим каждый из основных строительных блоков ООП-программы, использованной выше:

  • Классы
  • Объекты
  • Методы
  • Атрибуты

Классы

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

Классы содержат поля для атрибутов и методы для поведения. В нашем примере класса Dog атрибуты включают name и birthday, а методы включают bark() и updateAttendance().

Вот фрагмент кода, демонстрирующий, как запрограммировать класс Dog с помощью языка JavaScript.

class Dog {
    constructor(name, birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    //Declare private variables
    _attendance = 0;

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return Date.now() - this.birthday;
    }
    
    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }
}

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

Объекты

Неудивительно, что объекты — огромная часть ООП! Объекты — это экземпляры класса, созданные с использованием определенных данных. Например, в приведенном ниже фрагменте кода Rufus является экземпляром класса Dog.

class Dog {
    constructor(name, birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    //Declare private variables
    _attendance = 0;

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return Date.now() - this.birthday;
    }
    
    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }
}

//instantiate a new object of the Dog class, and individual dog named Rufus
const rufus = new Dog("Rufus", "2/1/2017");

Когда вызывается новый класс Dog:

  • Создается новый объект с именем rufus
  • Конструктор запускает аргументы name и birthday и присваивает значения

Словарь программирования:

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

У объектов есть состояния и поведение. Состояние объекта определяется данными: именами, датами рождения и другой информацией о собаке, которую вы хотели бы сохранить. Поведения – это методы, которые может выполнять объект.

Атрибуты

Атрибуты — это хранимая информация. Атрибуты определяются в шаблоне Class. Когда объекты создаются, отдельные объекты содержат данные, хранящиеся в поле Атрибуты.

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

Методы

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

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

class Dog {
    //Declare protected (private) fields
    _attendance = 0;

    constructor(name, birthday) {
        this.namee = name;
        this.birthday = birthday;
    }

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return this.calcAge();
    }

    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }
}

Методы часто изменяют, обновляют или удаляют данные. Однако методы не должны обновлять данные. Например, метод bark() не обновляет никаких данных, потому что лай не изменяет никаких атрибутов класса Dog: name или birthday.

Метод updateAttendance() добавляет день, когда Dog посещал лагерь для присмотра за домашними животными. Атрибут посещаемости важно отслеживать для выставления счетов Владельцам в конце месяца.

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

Подчеркивание в _attendance означает, что переменная защищена и не должна изменяться напрямую. Метод updateAttendance() изменяет _attendance.

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

Четыре столпа объектно-ориентированного программирования:

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

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

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

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

Например, пастушьи собаки обладают уникальной способностью пасти животных. Другими словами, все пастушьи собаки — собаки, но не все собаки — пастушьи собаки. Мы представляем эту разницу, создавая дочерний класс HerdingDog из родительского класса Dog, а затем добавляя уникальное поведение herd().

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

В следующем фрагменте кода дочерний класс HerdingDog наследует метод bark от родительского класса Dog, а дочерний класс добавляет дополнительный метод herd().

//Parent class Dog
class Dog{
    //Declare protected (private) fields
    _attendance = 0;

    constructor(namee, birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return this.calcAge();
    }

    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }
}

//Child class HerdingDog, inherits from parent Dog
class HerdingDog extends Dog {
    constructor(name, birthday) {
        super(name);
        super(birthday);
    }

    herd() {
        //additional method for HerdingDog child class
        return console.log("Stay together!")
    }
}

Обратите внимание, что в классе HerdingDog нет копии метода bark(). Он наследует метод bark(), определенный в родительском классе Dog.

Когда код вызывает метод fluffy.bark(), метод bark() проходит по цепочке от дочерних к родительским классам, чтобы найти, где определен метод bark.

//Parent class Dog
class Dog{
    //Declare protected (private) fields
    _attendance = 0;

    constructor(namee, birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return this.calcAge();
    }

    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }
}

//Child class HerdingDog, inherits from parent Dog
class HerdingDog extends Dog {
    constructor(name, birthday) {
        super(name);
        super(birthday);
    }

    herd() {
        //additional method for HerdingDog child class
        return console.log("Stay together!")
    }
}

//instantiate a new HerdingDog object
const fluffy = new HerdingDog("Fluffy", "1/12/2019");
fluffy.bark();

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

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

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

Метод herd() определен в дочернем классе HerdingDog, поэтому два объекта, Maisel и Fluffy, созданные из класса HerdingDog, имеют доступ к методу herd().

Rufus — это объект, созданный из родительского класса Dog, поэтому Rufus имеет доступ только к методу bark().

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

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

Затем, когда объект создается из класса, данные и методы инкапсулируются в этот объект. Инкапсуляция скрывает реализацию внутреннего программного кода внутри класса и скрывает внутренние данные внутри объектов.

Инкапсуляция требует определения некоторых полей как частных, а некоторых как общедоступных.

  • Частный/внутренний интерфейс: методы и свойства, доступные из других методов того же класса.
  • Открытый/внешний интерфейс: методы и свойства, доступные извне класса.

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

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

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

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

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

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

Примечание. В JavaScript есть частные и защищенные свойства и методы. Защищенные поля имеют префикс _; частные поля имеют префикс #. Защищенные поля наследуются. Частные — нет.

//Parent class Dog
class Dog{
    //Declare protected (private) fields
    _attendance = 0;

    constructor(namee, birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return this.calcAge();
    }

    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }
}

//instantiate a new instance of Dog class, an individual dog named Rufus
const rufus = new Dog("Rufus", "2/1/2017");
//use getter method to calculate Rufus' age
rufus.getAge();

Рассмотрим метод getAge() в нашем примере кода, детали расчета скрыты внутри класса Dog. Объект rufus использует метод getAge() для вычисления возраста Руфуса.

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

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

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

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

Преимущества инкапсуляции приведены здесь:

  • Повышает безопасность: извне доступны только общедоступные методы и атрибуты.
  • Защищает от распространенных ошибок. Доступны только общедоступные поля и методы, чтобы разработчики случайно не изменили что-то опасное.
  • Защищает интеллектуальную собственность: код скрыт в классе; сторонним разработчикам доступны только общедоступные методы
  • Поддерживается: большая часть кода обновляется и улучшается.
  • Скрывает сложность: никто не может видеть, что находится за занавеской объекта!

Абстракция

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

Абстракция

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

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

Водитель использует рулевое колесо, педали акселератора и тормоза для управления транспортным средством. Водителю не нужно беспокоиться о том, как работает двигатель или какие детали используются для каждого движения. Это абстракция — видны только важные аспекты, необходимые водителю для использования автомобиля.

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

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

Преимущества абстракции приведены ниже:

  • Простые пользовательские интерфейсы высокого уровня
  • Сложный код скрыт
  • Безопасность
  • Более простое обслуживание программного обеспечения
  • Обновления кода редко меняют абстракцию

Полиморфизм

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

Переопределение метода

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

Переопределение метода может создать метод bark() в дочернем классе, который переопределяет метод bark() в родительском классе Dog.

//Parent class Dog
class Dog{
    //Declare protected (private) fields
    _attendance = 0;

    constructor(namee, birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return this.calcAge();
    }

    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }
}

//Child class TrackingDog, inherits from parent
class TrackingDog extends Dog {
    constructor(name, birthday)
        super(name);
        super(birthday);
    }

    track() {
        //additional method for TrackingDog child class
        return console.log("Searching...")
    }

    bark() {
        return console.log("Found it!");
    }


//instantiate a new TrackingDog object
const duke = new TrackingDog("Duke", "1/12/2019");
duke.bark(); //returns "Found it!"

Перегрузка метода

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

//Parent class Dog
class Dog{
    //Declare protected (private) fields
    _attendance = 0;

    constructor(namee, birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    getAge() {
        //Getter
        return this.calcAge();
    }

    calcAge() {
        //calculate age using today's date and birthday
        return this.calcAge();
    }

    bark() {
        return console.log("Woof!");
    }

    updateAttendance() {
        //add a day to the dog's attendance days at the petsitters
        this._attendance++;
    }

    updateAttendance(x) {
        //adds multiple to the dog's attendance days at the petsitters
        this._attendance = this._attendance + x;
    }
}

//instantiate a new instance of Dog class, an individual dog named Rufus
const rufus = new Dog("Rufus", "2/1/2017");
rufus.updateAttendance(); //attendance = 1
rufus.updateAttendance(4); // attendance = 5

В этом примере кода, если в метод updateAttendance() не передаются никакие параметры. К счету прибавляется один день. Если параметр передается в updateAttendance(4), то 4 передается в параметр x в updateAttendance(x), и к счету добавляются 4 дня.

Преимущества полиморфизма:

  • Через один и тот же интерфейс можно передавать объекты разных типов.
  • Переопределение метода
  • Перегрузка метода

Заключение

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

Если вы хотите глубоко погрузиться в ООП, у Educative есть курсы по ООП в:

Эти курсы основаны на тексте со средой кодирования в браузере, поэтому вы можете учиться еще быстрее и эффективнее. Настройка не требуется; заходи и начинай кодить!

Удачного обучения!

Продолжить чтение об объектно-ориентированном программировании.

Начать обсуждение

На каком языке вы хотели бы дальше изучать ООП? Была ли эта статья полезна? Дайте нам знать в комментариях ниже!