Урок 88что такое ооп? объекты и классы(§46. что такое ооп? §47. объекты и классы)

Введение

Почти все популярные языки программирования являются объектно-ориентированными. В таблице приведены данные о популярности языков (рейтинг TIOBE) за сентябрь 2019 года :

Не являются объектно-ориентированными лишь 20% — это языки программирования аппаратуры (Си, Assembly language), декларативный язык программирования баз данных (SQL) и визуальный язык MATLAB. Не удивительно, что почти в каждом описании вакансии программиста требуется что-то типа «Понимание ООП» или «Понимание принципов SOLID» (что очень близко).

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

В этой статье-учебнике:

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

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

3 Что учить дальше?

Мы разобрали ценности и знаем к чему стоит стремиться, кроме того, мы поверхностно посмотрели на механизмы, которые предоставляет нам объектно-ориентированное программирование, не фокусируясь на каком-либо определенном языке

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

Изучите библиотеку модульного тестирования вашего языка программирования и попробуйте писать тесты.
Посмотрите литературу о шаблонах проектирования , при этом я рекомендую не пытаться запомнить детали реализации паттернов, а ответить на вопросы «где я могу применить этот шаблон?», «какую пользу и вред я с этого получу?». Если это кажется вам тяжелым — посмотрите как с помощью паттернов можно улучшить качество кода .

3.1 Список использованных источников

  1. Рейтинг популярности языков программирования TIOBE. URL: https://tiobe.com/tiobe-index/
  2. SOLID принципы. Рефакторинг. URL: https://pro-prof.com/archives/1914
  3. Почему мне кажется, что студентов учат ООП неправильно. URL: https://habr.com/ru/post/345658/
  4. C++ Russia 2018: Фёдор Короткий, Память – идеальная абстракция. URL: https://vk.com/wall-105242702_701
  5. Мейер Б. Объектно-ориентированное конструирование программных систем. М.: Издательско-торговый дом «Русская Редакция», «Интернет-университет информационных технологий», 2005. 1232 с.: ил.
  6. Мартин Р. Чистый код. Создание, анализ и рефакторинг. Библиотека программиста. – СПб.: Питер, 2014. – 464 с.
  7. Джейсон Мак-Колм Смит Элементарные шаблоны проектирования : Пер. с англ. — М. : ООО “И.Д. Вильямс”, 2013. — 304 с.
  8. Диаграммы классов UML. URL: https://pro-prof.com/archives/3212
  9. Юнит-тестирование. Пример. Boost Unit Test. URL: https://pro-prof.com/archives/1549
  10. Э. Гамма Приемы объектно-ориентированного проектирования. Паттерны проектирования / Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес. – СПб.: Питер, 2009. – 366 с.

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

Любая функция в программе представляет собой метод для объекта некоторого класса.
Класс должен формироваться в программе естественным образом, как только в ней возникает необходимость описания новых объектов программирования. Каждый новый шаг в разработке алгоритма должен представлять собой разработку нового класса на основе уже существующих.
Вся программа в таком виде представляет собой объект некоторого класса с единственным методом run (выполнить).
Программирование «от класса к классу» включает в себя ряд новых понятий. Основными понятиями ООП являются

  • инкапсуляция;
  • наследование;
  • полиморфизм.

Инкапсуляция данных (от «капсула») – это механизм, который объединяет данные и код, манипулирующий с этими данными, а также защищает и то, и другое от внешнего вмешательства или неправильного использования. В ООП код и данные могут быть объединены вместе (в так называемый «черный ящик») при создании объекта.
Внутри объекта коды и данные могут быть закрытыми или открытыми.
Закрытые коды или данные доступны только для других частей того же самого объекта и, соответственно, недоступны для тех частей программы, которые существуют вне объекта.
Открытые коды и данные, напротив, доступны для всех частей программы, в том числе и для других частей того же самого объекта.Наследование. Новый, или производный класс может быть определен на основе уже имеющегося, или базового класса.
При этом новый класс сохраняет все свойства старого: данные объекта базового класса включаются в данные объекта производного, а методы базового класса могут быть вызваны для объекта производного класса, причем они будут выполняться над данными включенного в него объекта базового класса.
Иначе говоря, новый класс наследует как данные старого класса, так и методы их обработки.
Если объект наследует свои свойства от одного родителя, то говорят об одиночном наследовании. Если объект наследует данные и методы от нескольких базовых классов, то говорят о множественном наследовании.
Пример наследования – определение структуры, отдельный член которой является ранее определенной структурой.Полиморфизм – это свойство, которое позволяет один и тот же идентификатор (одно и то же имя) использовать для решения двух и более схожих, но технически разных задач.
Целью полиморфизма, применительно к ООП, является использование одного имени для задания действий, общих для ряда классов объектов. Такой полиморфизм основывается на возможности включения в данные объекта также и информации о методах их обработки (в виде указателей на функции).
Будучи доступным в некоторой точке программы, объект , даже при отсутствии полной информации о его типе, всегда может корректно вызвать свойственные ему методы.Полиморфная функция – это семейство функций с одним и тем же именем, но выполняющие различные действия в зависимости от условий вызова.
Например, нахождение абсолютной величины в языке Си требует трех разных функций с разными именами:

123

int abs(int);long int labs(long int);double fabs(double);

Язык C++

Что за классы

Вот одно из фор­маль­ных опре­де­ле­ний клас­са: «Класс — это эле­мент ПО, опи­сы­ва­ю­щий абстракт­ный тип дан­ных и его частич­ную или пол­ную реа­ли­за­цию»

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

Если пока непо­нят­но, погру­жай­тесь в при­мер:

Сила примера

При­зо­вём на помощь силу при­ме­ров и пого­во­рим про сото­вые теле­фо­ны.

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

Но одно­го желе­за недо­ста­точ­но — нуж­но соеди­нить его меж­ду собой так, что­бы всё рабо­та­ло без сбо­ёв. Кро­ме это­го, нуж­но преду­смот­реть, что про­ис­хо­дит при нажа­тии на кноп­ки, что выво­дит­ся на экран и как поль­зо­ва­тель будет управ­лять этим теле­фо­ном.

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

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

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

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

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

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

Для чего нужно наследование в ООП, и какие преимущества оно даёт программированию? Главный плюс — повторное использование кода. Как правило, методы и поля, описанные в родительских классах, можно переиспользовать в классах-потомках. В результате:
— приходится писать меньше кода;
— повышается качество кода, он упрощается.

При этом сам механизм довольно гибок, поэтому недостающую в потомках функциональность всегда можно дописать отдельно.

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

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

Например:

Здесь при помощи ключевого слова из ES6 мы создаём класс  со свойствами и , которые хранятся в . Значения свойств задаются в конструкторе, а доступ к ним осуществляется в методе .

Конференция HolyJS 2020 Piter

10–11 апреля, Москва, 10 750–138 000 ₽

tproger.ru

События и курсы на tproger.ru

Мы создаём экземпляр класса с именем с помощью ключевого слова :

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

Для расширения класса мы можем создать другой класс. Расширим класс с помощью класса . — это с почтой и паролем:

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

Как говорит Дэн Абрамов:

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

Эрик Эллиот описал, как классовое наследование может потенциально привести к провалу проекта, а в худшем случае — к провалу компании:

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

Упрощенные конструкторы (с псевдокодом)

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

class Student {
    // описание класса учеников
    // ... прочий код ...
}

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

class Student {
    Student (String studentName, String Address, int ID) {
        // ... здесь храним вводимые данные и прочие внутрнние поля ...
    }
    // ...
}

Примечания

  1. Подпрограммы Эйфеля являются либо процедурами либо функциями. У процедур нет никакого возвращаемого типа. Функции всегда имеют возвращаемый тип.
  2. Поскольку должен быть также удовлетворён инвариант наследуемого(-х) класса(-ов), нет обязательного требования вызова родительских конструкторов.
  3. Полная спецификация содержится в стандартах ISO/ECMA по языку программироная Эйфель в онлайн доступе.
  4. Стандарт Эйфеля требует, чтобы поля были инициализированы при первом доступе к ним, т.ч. нет необходимости осуществлять их инициализацию значениями по умолчанию во время создания объекта.

Признаки плохого проекта

  • Закрепощённость: система с трудом поддается изменениям, поскольку любое минимальное изменение вызывает эффект «снежного кома», затрагивающего другие компоненты системы.
  • Неустойчивость: в результате осуществляемых изменений система разрушается в тех местах, которые не имеют прямого отношения к непосредственно изменяемому компоненту.
  • Неподвижность: достаточно трудно разделить систему на компоненты, которые могли бы повторно использоваться в других системах.
  • Вязкость: сделать что-то правильно намного сложнее, чем выполнить какие-либо некорректные действия.
  • Неоправданная сложность: проект включает инфраструктуру, применение которой не влечёт непосредственной выгоды.
  • Неопределенность: исходный код трудно читать и понимать. Недостаточно четко выражено содержимое проекта.

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

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

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

Теперь допустим, что мы играем в Sims. У нас есть семья из нескольких людей и кошки. Они общаются друг с другом. Мы хотим применить инкапсуляцию, поэтому инкапсулируем всю логику «cat» в класс Cat. Это выглядит следующим образом:

Здесь внутренним состоянием кошки являются частные(private) переменные: настроение(mood), голод(hungry) и энергия(energy). Она также имеет частный(private) метод meow (). Кошка может вызвать его в любой момент, когда захочет, другие классы не могут говорить кошке, когда ей можно мяукать.

То, что им можно делать, определяется в публичных(public) методах sleep (), play () и feed (). Каждый из них каким-то образом влияет на внутреннее состояние кошки и может вызвать meow (). Таким образом, устанавливается связь между внутренним состоянием объекта и публичными методами.

Вот что такое инкапсуляция.

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

Наследование позволяет создавать новые классы, которые повторно используют, расширяют и изменяют поведение, определенное в другом классе.Inheritance enables you to create a new class that reuses, extends, and modifies the behavior that is defined in another class. Класс, члены которого наследуются, называется базовым классом, а класс, который наследует эти члены, называется производным классом.The class whose members are inherited is called the base class, and the class that inherits those members is called the derived class. Следует учитывать, что все классы в C# неявно наследуются от класса Object, который поддерживает иерархию классов .NET и предоставляет низкоуровневые службы для всех классов.However, all classes in C# implicitly inherit from the Object class that supports .NET class hierarchy and provides low-level services to all classes.

Примечание

C# не поддерживает множественное наследование.C# doesn’t support multiple inheritance. То есть можно указать только один базовый класс для производного класса.That is, you can specify only one base class for a derived class.

Наследование от базового класса:To inherit from a base class:

По умолчанию унаследовать класс можно от любого класса.By default all classes can be inherited. Однако можно указать, должен ли класс использоваться в качестве базового класса, или создать класс, который может использоваться только в качестве базового.However, you can specify whether a class must not be used as a base class, or create a class that can be used as a base class only.

Указание, что класс не может использоваться в качестве базового класса:To specify that a class cannot be used as a base class:

Указание, что класс может использоваться только в качестве базового класса и нельзя создать экземпляр этого класса:To specify that a class can be used as a base class only and cannot be instantiated:

Дополнительные сведения можно найти в разделеFor more information, see:

переопределение членов;Overriding Members

По умолчанию производный класс наследует все члены от своего базового класса.By default, a derived class inherits all members from its base class. Если необходимо изменить поведение унаследованного члена, необходимо переопределить его.If you want to change the behavior of the inherited member, you need to override it. Т. е. в производном классе можно определить новую реализацию метода, свойства или события.That is, you can define a new implementation of the method, property or event in the derived class.

Следующие модификаторы используются для управления переопределением свойств и методов.The following modifiers are used to control how properties and methods are overridden:

Модификатор C#C# Modifier ОпределениеDefinition
virtualvirtual Разрешает переопределение члена класса в производном классе.Allows a class member to be overridden in a derived class.
overrideoverride Переопределяет виртуальный (переопределяемый) член в базовом классе.Overrides a virtual (overridable) member defined in the base class.
abstractabstract Требует, чтобы член класса был переопределен в производном классе.Requires that a class member to be overridden in the derived class.
Модификатор newnew Modifier Скрывает член, наследуемый от базового классаHides a member inherited from a base class

Как наследовать класс

Для начала создадим класс, от которого будем наследовать. Обычно его называют базовым или родительским:

Этот класс (Vehicle) представляет собой транспортное средство, но пока у него есть только слишком общие свойства (название, координаты и скорость) и поведение (перемещение). Нам может понадобиться реализовать класс, который тоже относится к транспортным средствам, но более конкретным. Например, это будет автомобиль (Car).

Если мы хотим, чтобы класс Car наследовал поля и методы класса Vehicle, то при его объявлении после названия нужно поставить двоеточие и имя родительского класса:

Теперь объекты класса Car обладают всеми полями и методами класса Vehicle:

ДелегатыDelegates

Делегат — это тип, который определяет сигнатуру метода и может обеспечивать связь с любым методом с совместимой сигнатурой.A delegate is a type that defines a method signature, and can provide a reference to any method with a compatible signature. Метод можно запустить (или вызвать) с помощью делегата.You can invoke (or call) the method through the delegate. Делегаты используются для передачи методов в качестве аргументов к другим методам.Delegates are used to pass methods as arguments to other methods.

Примечание

Обработчики событий — это ничто иное, как методы, вызываемые с помощью делегатов.Event handlers are nothing more than methods that are invoked through delegates. Дополнительные сведения об использовании делегатов при обработке событий см. в разделе События.For more information about using delegates in event handling, see Events.

Создание делегата:To create a delegate:

Создание ссылки на метод, сигнатура которого соответствует сигнатуре, указанной делегатом:To create a reference to a method that matches the signature specified by the delegate:

Дополнительные сведения см. в разделе руководства по программированию, посвященного делегатам, и в статье справочника по языку, посвященной ключевому слову delegate.For more information, see the programming guide article on Delegates and the language reference article on the delegate keyword.

Практика

Попробуйте сделать задание на классы из курса Погружение в Python:

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

Предположим есть данные о разных автомобилях и спецтехнике. Данные представлены в виде таблицы с характеристиками

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

Тип (car_type) Марка (brand) Кол-во пассажирских мест (passenger_seats_count) Фото (photo_file_name) Кузов ДxШxВ, м (body_whl) Грузоподъемность, Тонн (carrying) Дополнительно (extra)
car Nissan xTtrail 4 f1.jpeg 2.5
truck Man f2.jpeg 8x3x2.5 20
car Mazda 6 4 f3.jpeg 2.5
spec_machine Hitachi f4.jpeg 1.2 Легкая техника для уборки снега

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

  • BaseCar
  • Car(BaseCar)
  • Truck(BaseCar)
  • SpecMachine(BaseCar)

У любого объекта есть обязательный атрибут car_type. Он означает тип объекта и может принимать одно из значений: car, truck, spec_machine.

Также у любого объекта из иерархии есть фото в виде имени файла — обязательный атрибут photo_file_name.

В базовом классе нужно реализовать метод get_photo_file_ext для получения расширения файла (“.png”, “.jpeg” и т.д.) с фото. Расширение файла можно получить при помощи os.path.splitext.

Для грузового автомобиля необходимо разделить характеристики кузова на отдельные составляющие body_length, body_width, body_height. Разделитель — латинская буква x. Характеристики кузова могут быть заданы в виде пустой строки, в таком случае все составляющие равны 0

Обратите внимание на то, что характеристики кузова должны быть вещественными числами

Также для класса грузового автомобиля необходимо реализовать метод get_body_volume, возвращающий объем кузова в метрах кубических.

Все обязательные атрибуты для объектов Car, Truck и SpecMachine перечислены в таблице ниже, где 1 — означает, что атрибут обязателен для объекта, 0 — атрибут должен отсутствовать.

Car Truck SpecMachine
car_type 1 1 1
photo_file_name 1 1 1
brand 1 1 1
carrying 1 1 1
passenger_seats_count 1
body_width 1
body_height 1
body_length 1
extra 1

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

Не важно как вы назовете свои классы, главное чтобы их атрибуты имели имена:

  • car_type
  • brand
  • passenger_seats_count
  • photo_file_name
  • body_width
  • body_height
  • body_length
  • carrying
  • extra

И методы:

  • get_photo_file_ext
  • get_body_volume

У каждого объекта из иерархии должен быть свой набор атрибутов и методов. У класса легковой автомобиль не должно быть метода get_body_volume в отличие от класса грузового автомобиля.

Функция, которая парсит строки входного массива, должна называться get_car_list.

Также обратите внимание, что все значения в csv файле при чтении будут python-строками. Нужно преобразовать строку в int для passenger_seats_count, во float для carrying, а также во float для body_width body_height, body_length

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

Ссылка на основную публикацию