Наследование ооп: ООП с примерами (часть 2) / Хабр

Содержание

Урок #23 — Наследование классов (ООП)

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

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

За чем использовать наследование?

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

  • поля: скорость, вес, запас хода и тому подобное;
  • методы: получение информации из полей, установка новых значений;
  • конструктор: пустой и по установке всех полей.

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

Мы явно понимаем, что у объекта машина и самолёт будут разные поля и характеристики. Как можно поступить:

  1. Можно создать два отдельных класса: «Car» и «Airplane». В каждом классе будут все методы, поля и конструкторы повторно переписанные из класса «Транспорт», а также будут новые методы, что важны только для конкретного класса;
  2. Можно создать два класса наследника: «Car» и «Airplane». Оба класса будут наследовать всё от класса «Транспорт» и при этом будут содержать свои дополнительные функции. Таким образом повторения кода не будет и код станет меньше и чище.
Создание классов наследников

Для создания класса наследника требуется создать класс и указать наследование от главного класса.

Пример класса наследника:

class Transport {
protected:
	float speed;
};

class Auto : public Transport {
private:
	int wheels;
 
public:
	Auto (float speed, int wheels) {
		this->speed = speed;
		this->wheels = wheels;
		cout 

Как вы можете заметить, у нас есть одна общая переменная speed. Поскольку модификатор доступа у неё protected, то доступ к переменной мы имеем внутри класса Transport, а также внутри всех классов наследников.

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

14 Наследование в ООП . Виды наследования…

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

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

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

Единичное наследование (single inheritance)

Разработка ПО включает создание большого числа классов, многие из которых являются вариантами ранее созданных классов. Для управления потенциальной сложностью такой системы необходим механизм классификации, известный как наследование. Класс A будет наследником (heir) класса B, если он встраивает (наследует) компоненты класса B в дополнение к своим собственным. Потомок (descendant)- это прямой или непрямой наследник; обратное понятие — предок (ancestor).

Должно быть возможным объявить класс наследником другого класса.

Наследование — одно из центральных понятий ОО-метода; оно оказывает большое влияние на процесс разработки ПО.

Множественное наследование (Multiple inheritance)

Часто необходимо сочетать различные абстракции. Рассмотрим класс, моделирующий понятие «младенец». Его можно рассматривать как класс «человек» с компонентами, связанными с этим классом. Его же можно рассматривать и более прозаично — как класс «элемент, подлежащий налогообложению», которому положены скидки при начислении налогов. Наследование оправдано в обоих случаях. Множественное наследование (multiple inheritance) — это гарантия того, что класс может быть наследником не только одного класса, но многих, если это концептуально оправдано.

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

Класс должен иметь возможность быть наследником нескольких классов.

Конфликты имен при наследовании разрешаются адекватным механизмом.

например переименовании конфликтующих компонентов у класса наследника.

Дублируемое наследование (Repeated inheritance)

При множественном наследовании возникает ситуация дублируемого наследования (repeated inheritance), когда некоторый класс многократно становится наследником одного и того же класса, проходя по разным ветвям наследования:

Рис. 2.1. Дублируемое наследование

В этом случае язык должен обеспечить точные правила, определяющие, что происходит с компонентами, наследованными повторно от общего предка (на рисунке — это A). В некоторых случаях желательно, чтобы компонент из A создавал только один компонент в D (разделение), а в других — нужно, чтобы он создавал два (дублирование). Разработчики должны обладать гибкими средствами, позволяющими предписывать одну из возможностей независимо для каждого компонента.

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

Многоугольники и прямоугольники

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

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

При обеспечении расширяемости (extendibility) преимущество описанной выше системы типов состоит в гарантированной совместности во время компиляции, но она запрещает многие вполне законные комбинации элементов. Например, нельзя объявить массив, содержащий геометрические объекты различных совместных типов, таких как POINT (ТОЧКА) и SEGMENT(ОТРЕЗОК).

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

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

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

Многоугольники

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

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

indexing

description: «Многоугольники с произвольным числом вершин»

class POLYGON creation

feature — Доступ

count: INTEGER

— Число вершин

perimeter: REAL is

— Длина периметра

do … end

feature — Преобразование

display is

— Вывод многоугольника на экран.

do … end

rotate (center: POINT; angle: REAL) is

— Поворот на угол angle вокруг точки center.

do

… См. далее …

end

translate (a, b: REAL) is

— Сдвиг на a по горизонтали, на b по вертикали.

do … end

… Объявления других компонентов …

feature {NONE} — Реализация

vertices: LINKED_LIST [POINT]

— Список вершин многоугольника

invariant

same_count_as_implementation: count = vertices.count

at_least_three: count >= 3

— У многоугольника не менее трех вершин (см. упражнение У14.2)

end

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

Приведем реализацию типичной процедуры rotate. Эта процедура осуществляет поворот на заданный угол вокруг заданного центра поворота. Для поворота многоугольника достаточно повернуть по очереди каждую его вершину.

rotate (center: POINT; angle: REAL) is

— Поворот вокруг точки center на угол angle.

do

from

vertices.start

until

vertices.after

loop

vertices.item.rotate (center, angle)

vertices.forth

end

end

Чтобы понять эту процедуру заметим, что компонент item из LINKED_LIST возвращает значение текущего элемента списка. Поскольку vertices имеют тип LINKED_LIST [POINT], то vertices.item обозначает точку, к которой можно применить процедуру поворота rotate, определенную для класса POINT в предыдущей лекции. Это вполне корректно и достаточно общепринято — давать одно и то же имя (в данном случае rotate), компонентам разных классов, поскольку результирующее множество каждого из них имеет свой явно определенный тип. (Это ОО-форма перегрузки.)

Более важна для наших целей процедура вычисления периметра многоугольника. Единственный способ вычислить периметр многоугольника — это в цикле пройти по всем его вершинам и просуммировать длины всех ребер. Вот возможная реализация процедуры perimeter:

perimeter: REAL is

— Сумма длин ребер

local

this, previous: POINT

do

from

vertices.start; this := vertices.item

check not vertices.after end — Следствие условия at_least_three

until

vertices.is_last

loop

previous := this

vertices.forth

this := vertices.item

Result := Result + this.distance (previous)

end

Result := Result + this.distance (vertices.first)

end

В этом цикле просто последовательно складываются расстояния между соседними вершинами. Функция distance была определена в классе POINT. Значение Result, возвращаемое этой функцией, при инициализации получает значение 0. Из класса LINKED_LIST используются следующие компоненты: first дает первый элемент списка, start сдвигает курсор, на этот первый элемент, forth передвигает его на следующий, item выдает значение элемента под курсором, is_last определяет, является ли текущий элемент последним, after узнает, что курсор оказался за последним элементом. Как указано в команде check инвариант at_least_three обеспечивает правильное начало и завершение цикла. Он стартует в состоянии not after, в котором элемент vertices.item определен. Допустимо применение forth один или более раз, что, в конце концов, приведет в состояние, удовлетворяющее условию выхода из цикла is_last.

Прямоугольники

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

Преимущества такой смеси общих и специфических компонентов можно использовать, определив класс RECTANGLE как наследника (heir) класса POLYGON. При этом все компоненты класса POLYGON, называемого родителем (parent) класса RECTANGLE, по умолчанию будут применимы и к классу-наследнику. Для этого достаточно включить в RECTANGLE предложение наследования (inheritance clause):

class RECTANGLE inherit

POLYGON

feature

… Компоненты, специфичные для прямоугольников …

end

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

Вторая возможность полезна для такого компонента, который уже имелся у родителя, но у наследника должен быть описан в другом виде. Рассмотрим периметр perimeter. Для прямоугольников его можно вычислить более эффективно: не нужно вычислять четыре длины сторон, достаточно удвоить сумму длин двух сторон. Наследник, переопределяющий некоторый компонент родителя, должен объявить об этом в предложении наследования, включив предложение redefine:

class RECTANGLE inherit

POLYGON

redefine perimeter end

feature

end

Это позволяет включить в предложение feature класса RECTANGLE новую версию компонента perimeter, которая заменит его версию из класса POLYGON. Если не включить объявление redefine, то новое объявление компонента perimeter среди других компонентов класса RECTANGLE приведет к ошибке, поскольку у RECTANGLE уже есть компонент perimeter, унаследованный от POLYGON, т.е. у некоторого компонента окажется два определения.

Класс RECTANGLE выглядит следующим образом:

indexing

description: «Прямоугольники, — специальный случай многоугольников»

class RECTANGLE inherit

POLYGON

redefine perimeter end

creation

make

feature — Инициализация

make (center: POINT; s1, s2, angle: REAL) is

— Установить центр прямоугольника в center, длины сторон

— s1 и s2 и ориентацию angle.

do … end

feature — Access

side1, side2: REAL

— Длины двух сторон

diagonal: REAL

— Длина диагонали

perimeter: REAL is

— Сумма длин сторон

— (Переопределение версии из POLYGON)

do

Result := 2 S (side1 + side2)

end

invariant

four_sides: count = 4

first_side: (vertices.i_th (1)).distance (vertices.i_th (2)) = side1

second_side: (vertices.i_th (2)).distance (vertices.i_th (3)) = side2

third_side: (vertices.i_th (3)).distance (vertices.i_th (4)) = side1

fourth_side: (vertices.i_th (4)).distance (vertices.i_th (1)) = side2

end


Для списка i_th(i) дает элемент в позиции i ( i-й элемент, следовательно это имя запроса).

Так как RECTANGLE является наследником класса POLYGON, то все компоненты родительского класса применимы и к новому классу: vertices, rotate, translate, perimeter (в переопределенном виде) и все остальные. Их не нужно повторять в определении нового класса.

Этот процесс транзитивен: всякий класс, будучи наследником RECTANGLE, например, SQUARE, также обладает всеми компонентами класса POLYGON.

Основные соглашения и терминология

Кроме терминов «наследник» и «родитель» будут полезны следующие термины:

Терминология наследования

Потомок класса C — это любой класс, который наследует C явно или неявно, включая и сам класс C. (Формально, это либо C, либо, по рекурсии, потомок некоторого наследника C).

Собственный потомок класса C — это потомок, отличный от самого C.

Предок C — это такой класс A, для которого C является потомком. Собственный предок C — это такой класс A, для которого C является собственным потомком.

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

Имеется также терминология для компонентов класса: компонент либо является наследуемым (перешедшим от некоторого собственного предка), либо непосредственным (введенным в данном классе).

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

Рис. 14.1. Связь по наследованию

Переопределяемый компонент отмечается ++ (это соглашение принято в Business Object Notation (B.O.N.)).

Стрелка указывает вверх от наследника к родителю. Это соглашение легко запомнить — оно представляет отношение «наследовать от». В литературе встречается и обратное направление таких стрелок. Хотя обычно выбор графического представления является делом вкуса, в данном случае, одно из них явно лучше другого, поскольку одно наводит на мысль о правильном отношении, а другое может привести к путанице. Стрелка — это не просто произвольная пиктограмма, она указывает на одностороннюю связь между своими двумя концами. В данном случае:

[x]. Всякий экземпляр наследника можно рассматривать как экземпляр родителя, а обратное неверно.

[x]. В тексте наследника всегда упоминается его родитель, но не наоборот. Это, на самом деле, является важным свойством ОО-метода, вытекающим из принципа Открыт-Закрыт, согласно которому класс не «знает» списка своих наследников и других собственных потомков.

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

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

Хотелось бы указать инвариант класса RECTANGLE, который говорил бы, что число сторон прямоугольника равно четырем и что длины сторон последовательно равны side1, side2, side1 и side2.

У класса POLYGON также имеется инвариант, который применим и к его наследнику:

Правило наследования инварианта

Инвариант класса является конъюнкцией утверждений из его раздела invariant и свойств инвариантов его родителей (если таковые имеются).

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

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

В нашем примере второе предложение (at_least_three) инварианта POLYGON утверждает, что число сторон должно быть не менее трех, оно является следствием предложения four_sides из инварианта класса RECTANGLE, которое требует, чтобы сторон было ровно четыре.

Наследование и конструкторы

Ранее не показанная процедура создания (конструктор) для класса POLYGON может иметь вид

make_polygon (vl: LINKED_LIST [POINT]) is

— Создание по вершинам из vl.

require

vl.count >= 3

do

…Инициализация представления многоугольника по элементам из vl …

ensure

— vertices и vl состоят из одинаковых элементов (это можно выразить

формально)

end

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


Ей дано собственное имя make_polygon, чтобы избежать конфликта имен при ее наследовании классом RECTANGLE, у которого имеется собственная процедура создания make. Мы не рекомендуем так делать в общем случае, в следующей лекции будет показано, как давать процедуре создания класса POLYGON стандартное имя make, а затем использовать переименование в предложении о наследовании класса RECTANGLE, чтобы предотвратить коллизию имен.

Приведенная выше процедура создания класса RECTANGLE имеет четыре аргумента: точку, служащую центром, длины двух сторон и ориентацию. Отметим, что компонент vertices применим к прямоугольникам, поэтому процедура создания для RECTANGLE создает список вершин vertices (четыре угла вычисляются по центру, длинам сторон и ориентации).

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

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

Правило наследования конструктора

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

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

В некоторых случаях родительский конструктор подходит и для наследника. Тогда его просто нужно указать в предложении creation:

class B inherit

A

creation

make

feature

где процедура make наследуется без изменений от класса A, у которого она также указана в предложении creation.

Пример иерархии

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

Рис. 14.2. Иерархия типов фигур

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

Рядом с классами указаны их разные компоненты. Символ «++» означает «переопределено», а символы «+» и «*» будут объяснены далее.

Ранее для простоты RECTANGLE был наследником класса POLYGON. Поскольку указанная классификация основана на числе вершин, то представляется разумным ввести промежуточный класс QUADRANGLE для четырехугольников на том же уровне, что и классы TRIANGLE, PENTAGON и т. п. Тогда компонент diagonal (диагональ) можно переместить на уровень класса QUADRANGLE.

Отметим, что класс SQUARE, наследник класса RECTANGLE, характеризуется инвариантом side1 = side2. Аналогично, у эллипса имеются два фокуса, а у круга они сливаются в один, что определяет инвариант класса CIRCLE: equal (focus1 = focus2).

Полиморфизм

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

Полиморфное присоединение

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

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

p: POLYGON; r: RECTANGLE; t: TRIANGLE

Тогда допустимы следующие присваивания:

p := r

p := t

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

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

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

Приведенные в примере полиморфные присваивания легитимны, поскольку структура наследования позволяет рассматривать экземпляр класса RECTANGLE или TRIANGLE как экземпляр класса POLYGON. Мы говорим, что в таком случае тип источника согласован с типом цели. В обратном направлении присваивание недопустимо, т.е. некорректно писать r := p. Вскоре это важное правило будет рассмотрено более подробно.

Кроме присваивания, полиморфизм имеет место и при передаче аргументов, например в вызовах вида f (r) или f (t) при условии объявлении компонента f в виде:

f (p: POLYGON) is do … end

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

Что на самом деле происходит при полиморфном присоединении?

Все сущности, встречающиеся в предыдущих примерах полиморфных присваиваний, имеют тип ссылок: возможными значениями p, r и t являются не объекты, а ссылки на объекты. Поэтому результатом присваивания p := r является просто новое присоединение ссылки.

Рис. 14.3. Полиморфное присоединение ссылки

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

Полиморфные присоединения допускаются только для целей типа ссылки, но, ни в коем случае, для расширенных типов. Поскольку у класса-потомка могут быть новые атрибуты, то соответствующие ему экземпляры могут иметь больше полей. На рис. 14.3 видно, что объект класса RECTANGLE больше, чем объект класса POLYGON. Такая разница в размерах объектов не приводит к проблемам, если все, что заново присоединяется, имеет тип ссылки. Но если p — не ссылка, а имеет развернутый тип (например, объявлена как expanded POLYGON), то значением p является непосредственно некоторый объект, и всякое присваивание p будет менять содержимое этого объекта. В этом случае никакой полиморфизм невозможен.

Полиморфные структуры данных

Рассмотрим массив многоугольников:

poly_arr: ARRAY [POLYGON]

Когда некоторое значение x присваивается элементу этого массива, как в вызове

poly_arr.put (x, some_index)

(для некоторого допустимого значения индекса some_index), то спецификация класса ARRAY указывает, что тип присваиваемого значения должен быть согласован с типом фактического родового параметра:

class ARRAY [G] creation

feature — Изменение элемента

put (v: G; i: INTEGER) is

— Присвоить v элементу с индексом i

end

Так как тип формального аргумента v, соответствующего x, в классе определен как G, а фактический родовой параметр, соответствующий G в вызове poly_arr, — это POLYGON, то тип x должен быть согласован с ним. Как мы видели, для этого x не обязан иметь тип POLYGON, подойдет любой потомок типа POLYGON.

Поэтому, если границы массива равны 1 и 4, то можно объявить некоторые сущности:

p: POLYGON; r: RECTANGLE; s: SQUARE; t: TRIANGLE

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

poly_arr.put (p, 1)

poly_arr.put (r, 2)

poly_arr.put (s, 3)

poly_arr.put (t, 4)

которые присвоят элементам массива ссылки на объекты различных типов.

Рис. 14.4. Полиморфный массив


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

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

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

Рис. 14.5. Измерения обобщения

Типы, которые на рис. 10.1 неформально назывались SET_OF_BOOKS и т. п., заменены типами, выведенными из родового универсального типа, — SET [BOOK].

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

LIST [RECTANGLE]: может содержать квадраты, но не треугольники.

LIST [POLYGON]: может содержать квадраты, прямоугольники, треугольники, но не круги.

LIST [FIGURE]: может содержать экземпляры любого типа из иерархии FIGURE, но не книги или банковские счета.

LIST [ANY]: может содержать объекты любого типа.

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

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

Типизация при наследовании

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

Согласованность типов

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

p: POLYGON

r: RECTANGLE

Выделим в приведенной выше иерархии нужный фрагмент (рис. 14.6).

Тогда законны следующие выражения:

[x]. p.perimeter: никаких проблем, поскольку perimeter определен для многоугольников;

[x]. p.vertices, p.translate (…), p.rotate (…) с корректными аргументами;

[x]. r.diagonal, r.side1, r.side2: эти три компонента объявлены на уровне RECTANGLE или QUADRANGLE;

[x]. r.vertices, r.translate (…), r.rotate (…): эти компоненты объявлены на уровне POLYGON или еще выше и поэтому применимы к прямоугольникам, наследующим все компоненты многоугольников;

[x]. r.perimeter: то же, что и в предыдущем случае. Но у вызываемой здесь функции имеется новое определение в классе RECTANGLE, так что она отличается от функции с тем же именем из класса POLYGON.

Рис. 14.6. Фрагмент иерархии геометрических фигур

А следующие вызовы компонентов незаконны, так как эти компоненты недоступны на уровне многоугольника:

p.side1

p.side2

p.diagonal

Это рассмотрение основано на первом фундаментальном правиле типизации:

Правило Вызова Компонентов

Если тип сущности x основан на классе С, то в вызове компонента x.f сам компонент f должен быть определен в одном из предков С.

Напомним, что класс С является собственным предком. Фраза «тип сущности x основан на классе С» напоминает, что для классов, порожденных из родовых, тип может включать не только имя класса: LINKED_LIST [INTEGER]. Но базовый класс для типа — это LINKED_LIST, так что родовой параметр никак не участвует в нашем правиле.

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

Статическая типизация — это один из главных ресурсов ОО-технологии для достижения объявленной в 1-ой лекции цели — надежности ПО.

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

продолжение следует…

Продолжение:

Часть 1 14 Наследование в ООП . Виды…
Часть 2 14 Наследование в ООП . Виды…
Часть 3 14 Наследование в ООП . Виды…
Часть 4 14 Наследование в ООП . Виды…

Наследование | Python: Введение в ООП

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

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

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

Когда один класс становится наследником другого, то все атрибуты класса-предка (надкласса, superclass) становятся доступны классу-потомку (подклассу, subclass) — наследуются (достаются в наследство).

Что даёт наследование

Наследование позволяет выделить общее для нескольких классов поведение и вынести его в отдельную сущность. То есть наследование является средством переиспользования кода (code reuse) — использования существующего кода для решения новых задач!

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

Как обычно, рассмотрим пример:

# этот класс у нас уже был
class Counter:
    def __init__(self):
        self.value = 0

    def inc(self):
        self.value += 1

    def dec(self):
        self.value -= 1

# А этот класс - новый. Унаследованный от Counter
class NonDecreasingCounter(Counter):  # в скобках указан класс-предок
    def dec(self):
        pass

Если мы выполним эти объявления классов и посмотрим на поведение экземпляра NonDecreasingCounter, то увидим, что он работает как Counter — имеет те же методы и атрибуты (правда, при вызове метода .dec новый счётчик не изменяет текущее значение):

>>> n = NonDecreasingCounter()
>>> n.inc()
>>> n.inc()
>>> n.value
2
>>> n.dec()
>>> n.value
2

В объявлении NonDecreasingCounter присутствует метод dec, а вот откуда взялись value и inc? Они были взяты от предка — класса Counter! Данный факт даже можно пронаблюдать:

>>> n.dec
<bound method NonDecreasingCounter.dec of <__main__.NonDecreasingCounter object at 0x7f361b29c940>>
>>> n.inc
<bound method Counter.inc of <__main__.NonDecreasingCounter object at 0x7f361b29c940>>

Метод dec — метод класса NonDecreasingCounter, связанный с конкретным экземпляром NonDecreasingCounter. А вот inc — метод класса Counter, хоть и связанный с всё тем же экземпляром класса-потомка.

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

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

Всё будет

super()

Представим, что нас в целом устраивает класс Counter из предыдущего примера, но мы хотим при вызове inc увеличивать значение дважды. Мы могли бы в потомке заменить весь метод и делать внутри нового метода self.value += 2. Но если бы позже что-то поменялось в исходном классе Counter, то эти изменения не коснулись бы нашего метода.

Получается, что нам внутри метода потомка нужно получить доступ к методу предка. Методу с тем же именем! Если мы просто обратимся к self.inc, то получим ссылку на новый метод, ведь мы его переопределили.

Тут нам на помощь приходит специальная функция super:

class DoubleCounter(Counter):
    def inc(self):
        super().inc()
        super().inc()

Вызов super здесь заменяет обращение к self. При этом вы фактически обращаетесь к «памяти предков»: получаете ссылку на атрибут предка. Более того, что super().inc здесь, это именно связанный с текущим экземпляром метод, то есть полноценная «оригинальная версия»! Если бы вы вдруг решили вручную вызвать метод класса предка, то вам бы пришлось использовать его не связанную версию:

class DoubleCounter(Counter):
    def inc(self):
        Counter.inc(self)  # явно обращаемся к методу класса предка
        Counter.inc(self)  # и передаём ссылку на экземпляр

Вызов super вместо явного вызова предка хорош не только тем, что автоматически связывает методы. При смене предка (такое бывает) в описании класса super учтёт изменения и вы получите доступ к поведению нового предка. Удобно!

super работает не только с методами, но и с атрибутами классов:

>>> class A:
...     x = 'A'
... class B(A):
...     x = 'B'
...     def super_x(self):
...         return super().x
...
>>> B().x
'B'
>>> B().super_x()
'A'

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

Функция super так названа в честь названия класса-предка: «superclass».

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

object

В прошлом мы не указывали предка в объявлениях классов, то есть писали так:

class Foo:
    pass

В Python3 такая запись равнозначна записи class Foo(object):. То есть, если класс-предок не указан, то таковым считается object — самый базовый класс в Python. Сейчас, в эпоху повсеместного использования Python3, указывать или не указывать наследование от object — дело вкуса.

Инкапсуляция, наследование, полиморфизм | Python 3 для начинающих и чайников

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

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

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

Инкапсуляция в Python работает лишь на уровне соглашения между программистами о том, какие атрибуты являются общедоступными, а какие — внутренними.

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

class A:
    def _private(self):
        print("Это приватный метод!")

>>> a = A()
>>> a._private()
Это приватный метод!

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

>>> class B:
...     def __private(self):
...         print("Это приватный метод!")
...
>>> b = B()
>>> b.__private()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute '__private'

Однако полностью это не защищает, так как атрибут всё равно остаётся доступным под именем _ИмяКласса__ИмяАтрибута:

>>> b._B__private()
Это приватный метод!

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

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

>>> class Mydict(dict):
...     def get(self, key, default = 0):
...         return dict.get(self, key, default)
...
>>> a = dict(a=1, b=2)
>>> b = Mydict(a=1, b=2)

Класс Mydict ведёт себя точно так же, как и словарь, за исключением того, что метод get по умолчанию возвращает не None, а 0.

>>> b['c'] = 4
>>> print(b)
{'a': 1, 'c': 4, 'b': 2}
>>> print(a.get('v'))
None
>>> print(b.get('v'))
0

Полиморфизм

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

>>> 1 + 1
2
>>> "1" + "1"
'11'

Три основных принципа ООП: наследование, инкапсуляция, полиморфизм

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

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

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

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

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

Этот принцип сокрытия — второй принцип объектно-ориентированного программирования, называемый инкапсуляцией.

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

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

Замещать можно и переменные. В подобном разноликом поведении и заключается третий принцип объектно-ориентированного программирования — Полиморфизм.

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

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

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

Delphi.

События используются в Delphi для обеспечения определения программистом реакции на те или иные действия или запросы со стороны компонента.

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

Программа Delphi не является сплошным последовательным выполнением кода, как в Turbo Pascal. Здесь программа реализует некоторый код только как реакцию на события -какие-то действия пользователя (нажатие кнопок, движение мыши, закрытие окон и т.п.). Когда код, описанный в обработчике, заканчивается, программа не завершается. Для завершения требуется, чтобы пользователь обычным в Windows способом закрыл главное окно приложения либо, к примеру, нажал на сделанную кнопку, в обработчике нажатия которой предусмотрен вызов процедуры Close.

⇐1.2. Инспектор объектов || Оглавление || 3. Использование справки⇒

Объектно-ориентированное программирование | Flexberry PLATFORM Documentation

Краткое описание

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

Основные понятия

  • Абстракция данных — Абстрагирование означает выделение значимой информации и исключение из рассмотрения незначимой. В ООП рассматривают лишь абстракцию данных (нередко называя её просто «абстракцией»), подразумевая набор значимых характеристик объекта, доступный остальной программе.
  • Инкапсуляция — свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе. Одни языки (например, С++, Java или Ruby) отождествляют инкапсуляцию с сокрытием, но другие (Smalltalk, Eiffel, OCaml) различают эти понятия.
  • Наследование — свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью. Класс, от которого производится наследование, называется базовым, родительским или суперклассом. Новый класс — потомком, наследником, дочерним или производным классом.
  • Полиморфизм подтипов (полиморфизм) — свойство системы, позволяющее использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта. Другой вид полиморфизма — параметрический — в ООП называют обобщённым программированием.
  • Класс — универсальный, комплексный тип данных, состоящий из тематически единого набора «полей» (переменных более элементарных типов) и «методов» (функций для работы с этими полями), то есть он является моделью информационной сущности с внутренним и внешним интерфейсами для оперирования своим содержимым (значениями полей). В частности, в классах широко используются специальные блоки из одного или чаще двух спаренных методов, отвечающих за элементарные операции с определенным полем (интерфейс присваивания и считывания значения), которые имитируют непосредственный доступ к полю.
  • Объект — сущность в адресном пространстве вычислительной системы, появляющаяся при создании экземпляра класса (например, после запуска результатов компиляции и связывания исходного кода на выполнение).

Пример использования

Схема принципов ООП

Ресурсы

Перейти

ООП в JavaScript | Frontend Stuff

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

В ООП объект представляет собой блок, содержащий информацию (состояние / атрибуты) и операции (методы).

Ключевое слово

this

  • this — это объект, свойством которого является функция;
  • this — дает функциям доступ к своему объекту и его свойствам;
  • this — помогает выполнить один и тот же код для нескольких объектов;
  • this — можно рассматривать как кто меня вызвал?; т.е. то, что находится слева от точки. Например, window.a();
  • this — имеет динамическую область, т. е. не важно, где он был написан, важно, где он был вызван.
const obj = {
  name: 'Alex',
  sing() {
    console.log('a this ', this);
    var anotherFunc = function() {
      console.log('b this ', this);
    }
    anotherFunc();
  }
};

obj.sing();


var b = {
  name: 'jay',
  say() {
    console.log('this is ', this);
  }
}
b.say()


var c = {
  name: 'jay',
  say() {
    return function () {
      console.log('this is ', this);
    }
  }
}
c.say()()


var d = {
  name: 'jay',
  say() {
    
    return () => console.log('this is ', this);
  }
}
d.say()()

Стрелочные функции связывают this с лексической областью действия.

const obj = {
  name: 'Alex',
  sing() {
    console.log('a this ', this);
    var anotherFunc = () => {
      console.log('b this ', this);
    }
    anotherFunc();
  }
};

obj.sing();


Прототип

  • Прототип (prototype) — это экземпляр рабочего объекта. Объекты наследуются напрямую от других объектов.
  • __proto__ является ссылкой на свойство прототипа родительского объекта, например:
const obj = {};
obj.__proto__ === Object.prototype 
  • Свойство prototype принадлежит только функциям, в частности, функциям конструктора. Конструктор Object создает обертку объекта.
  • Свойства proto и prototype используются для создания цепочки наследования свойств между объектами, начиная с Object и Primitive Types.
  • Object.create() можно использовать для создания объектов с его свойством proto, связанным со свойством prototype объекта, переданного в качестве аргумента Object.create().
  • Object — это базовая функция (конструктор). Корнем всего в JavaScript является Object, который на самом деле является функцией.

Object имеет свойство prototype, которое является базовым объектом для всех вещей в JavaScript, включая функции JavaScript.

ES6 Классы

  • Ключевое слово class в JS — синтаксический сахар. Под капотом он всё еще использует прототипное наследование (prototypal inheritance).
  • Экземпляры класса должны создаваться с ключевым словом new.
  • Метод constructor используется для создания экземпляра state (данных) нового объекта. State обычно уникально для каждого экземпляра.
  • Функции обычно не включаются в конструктор, так как они создают ссылку на место в памяти в каждом новом экземпляре класса. Таким образом используя больше памяти, чем необходимо. Включая функции в качестве методов класса, экземпляры класса могут ссылаться на функцию через цепочку прототипов.
  • Прототипное наследование (prototypal inheritance) имеет лучшую эффективность памяти, чем классическое наследование, благодаря тому, что оно разделяет ссылки памяти своих свойств прототипа с теми объектами, которые наследуют от него. В классическом наследовании, экземпляры класса создают новые ссылки на память для каждого унаследованного свойства.

Object.create() vs. Classes

  • Оба Object.create() и class являются способами создания цепочки прототипов.
  • Некоторые люди предпочитают избегать ключевые слова constructor, class и this, чтобы ограничить путаницу из-за this.
  • Другие предпочитают использовать ключевые слова constructor, class и this, возможно, из-за его сходства с другими языками с парадигмой объектно-ориентированного программирования.

Private vs. Public vs. Protected

Во многих объектно-ориентированных языках программирования, которые имеют классы, идея private и public полей действительно важна. В JavaScript этого нет. Ранее, если нужно было сделать поле private, к которому нельзя обращаться из класса, мы добавляли подчеркивание _ перед именем, чтобы другие программисты знали, что это private метод. Но, к сожалению, подчеркивание на самом деле ничего не делает.

В JavaScript есть предложение ECMAScript, которое предназначено для объявлений полей класса.

Это модификаторы доступа, которые помогают нам реализовать Encapsulation (или скрытие информации). Они сообщают компилятору, какие другие классы должны иметь доступ к определенному полю или методу.

Private — только текущий класс будет иметь доступ к полю или методу.
Protected — только текущий класс и подклассы этого класса будут иметь доступ к полю или методу.
Public — любой класс может ссылаться на поле или вызывать метод.

Так как в Javascript таких полей пока нет, для их реализации мы можем использовать TypeScript.

4 принципа ООП

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

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

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

Инкапсуляция с использованием замыкания
const createCounter = () => {
  
  
  let count = 0;

  return ({
    
    
    
    click: () => count += 1,
    getCount: () => count.toLocaleString()
  });
};

const counter = createCounter();

counter.click();
counter.click();
counter.click();

console.log(counter.getCount()); 

Абстракция

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

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

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

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

Полиморфизм

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

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

Чтобы это произошло полиморфизм использует наследование.

В следующем примере дочерний объект, такой как Coder, переопределяет метод say, вызванный из родительского объекта Human, и возвращает новую строку соответственно. Тогда как другой дочерний объект Men, вместо переопределения метода say, наследует его и отображал родительскую строку.

class Human {
  constructor(name) {
    this.name = name;
  }

  say() {
    return `Hello, my name is ${this.name}, I like travelling`;
  }
}

class Men extends Human {
  constructor(name) {
    super(name)
  }
  
}

class Coder extends Human {
  constructor(name) {
    super(name)
  }

  say() {
    
    return `Hello, my name is ${this.name}, I like coding`;
  }
}

const alex = new Men('Alex');
const leo = new Coder('Leo');

alex.say() 
leo.say() 

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

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

class Human {
  constructor(name) {
    this.name = name;
  }

  sayMyName() {
    return 'Hello, I am ' + this.name;
  }
}

class Men extends Human {
  constructor(name) {
    super(name)
  }
}
class Coder extends Human {
  constructor(name) {
    super(name)
  }
}

const alex = new Men('Alex');
const leo = new Coder('Leo');

alex.sayMyName() 
leo.sayMyName() 
Prototypal Inheritance

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

Classical Inheritance

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

Tight Coupling (сильная связанность) относится к волновым эффектам, которые могут произойти с подклассами (дочерние классы), когда вносится изменение в суперкласс (родительский класс).

  • Tight Coupling может привести ко многим непреднамеренным эффектам для подклассов.
  • Tight Coupling может помочь избежать повторений в коде.
  • Хрупкая проблема базового класса является фундаментальной архитектурной проблемой систем объектно-ориентированного программирования, в которых базовые классы (суперклассы) считаются «хрупкими», потому что, казалось бы, безопасные модификации базового класса, когда они наследуются производным классом, могут привести к сбоям в работе производных классов.
  • Проблема гориллы с бананом относится к проблеме наследования слишком много от суперкласса. «Будто тебе нужен банан, а ты получаешь банан в придачу с гориллой в джунглях».
  • Классическое наследование требует превосходного предвидения, чтобы избежать проблем неправильного наследования.

Что такое наследование? Простое объяснение ООП | Эндрю Кениг-Баутиста

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

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

Чем это полезно? Большая часть пользы от наследования — это возможность многократного использования.Я собираюсь использовать в качестве примера Ноя и ковчег. Для тех из вас, кто не знаком с этой историей, Земле угрожает всемирный потоп, и Ной строит ковчег, чтобы хранить по два из всех наземных животных на Земле, чтобы они пережили шторм. Предположим, Ной хотел использовать программу, которая поможет ему отслеживать всех животных в ковчеге и их потребности.

На фото: исторически точный художественный вариант Ноева ковчега (фото Дэна Кб на Unsplash)

Создание уникального класса для каждого отдельного животного быстро стало бы очень повторяющимся, потому что есть некоторые свойства и поведение, которые применимы к каждому отдельному животному, от мыши слону.Общие функции могут включать feed (), hydrate (), cleanEnclosure (). Вместо того, чтобы создавать эти общие атрибуты снова и снова для каждого животного, мы могли бы вместо этого создать родительский класс Animal! Этот родительский класс будет содержать свойства и поведение, универсальные для всех животных, и избавит нас от необходимости создавать эти общие функции до бесконечности.

, возможно, будет полезна функция dontGetEaten () (Фото Андре Климке на Unsplash)

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

На следующей неделе: полиморфизм!

ООП — Наследование и полиморфизм в Python | автор: Jayashree domala

Фото Meagan Carsience на Unsplash

Руководство по детальному изучению концепции объектно-ориентированного программирования наследования и полиморфизма в Python

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

Мы начинаем с создания базового класса под названием «animal», который не принимает никаких аргументов и просто печатает оператор, используя метод «init». Затем создаются еще 2 метода.

  >>> class  Animal (): 
def __init __ (self):
print («Создан класс Animal») def guess_who (self):
print («Я - животное») def sleep (self):
print («Я сплю») >>> my_animal = Animal ()
Создан класс Animal >>> my_animal.sleep ()
Я сплю

Вы можете увидеть методы созданного экземпляра.

Теперь новые классы, если они созданы, могут наследовать некоторые методы этого базового класса, если это необходимо. Подобно тому, как нам нужен класс «Кошка», тогда некоторые особенности класса животных полезны для класса собак. Таким образом, мы можем унаследовать базовый класс «Animal». Мы передаем «Animal» в качестве аргумента при создании производного класса. Затем определите метод «init», в котором мы вызываем метод «Animal__init__». Таким образом, мы создаем экземпляр класса Animal, когда создаем экземпляр класса «Cat».И вы заметите, что методы класса «Animal» будут производными от класса «Cat». Вы также можете перезаписать существующие методы. Просто используйте то же имя. Также возможно добавление новых методов.

  >>> class  Cat (Animal): 
def __init __ (self):
Animal .__ init __ (self)
print ("Класс Cat создан") def guess_who (self):
print (" Я кот ") def meow (self):
print (" MEOWW! ") >>> my_cat = Cat ()
Создан класс Animal
Создан класс Cat >>> my_cat.guess_who ()
Я кот >>> my_cat.meow ()
MEOWW!

Вы можете увидеть методы для экземпляра, созданного для класса «Cat».

Это относится к способу, которым разные классы объектов могут использовать одно и то же имя метода. Мы создаем два класса «Собака» и «Кошка», которые имеют один и тот же метод «говорить». Когда мы вызываем метод speak каждого объекта, он возвращает уникальный для объекта результат.

  >>> class  Doggy (): 
def __init __ (self, name):
self.name = name def speak (self):
return self.name + "говорит WOOF" >>> class Catty ():
def __init __ (self, name):
self .name = name def speak (self):
return self.name + "говорит MEOW" >>> my_dog = Doggy ("Buzo")
>>> my_cat = Catty ("Тим") >>> print (my_dog.speak ())
Бузо говорит WOOF >>> print (my_cat.Speak ())
Тим говорит MEOW

Итак, обратите внимание, что my_dog и my_cat имеют одно и то же имя метода, называемое speak, но у них разные типы классов, как видно.

  >>> для  питомца  в  [my_dog, my_cat]: 
print (type (pet))
print (type (pet.speak ()))
print (pet.speak ())

Buzo говорит: WOOF


Тим говорит, что MEOW

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

  >>> class  Animal (): 
def __init __ (self, name):
self.name = name def speak (self):
raise NotImplementedError ("Подкласс должен реализовать это метод abtract ") >>> my_animal = Animal (" Tia ") >>> my_animal.speak () ------------------------ -------------------------------------------------- -
NotImplementedError Traceback (последний вызов последним)
in
----> 1 my_animal . говорить () говорить (сам)
5
6 def говорить ( self ):
----> 7 повысить NotImplementedError («Подкласс должен реализовывать этот метод абстрагирования») NotImplementedError : Подкласс должен реализовывать этот метод абстрагирования

Итак, теперь возникает ошибка, поскольку этот класс никогда не должен был создаваться. Это абстрактный класс, потому что в базовом классе он ничего не делает и ожидает, что вы унаследуете этот класс и перезапишете метод.Фактически, нам нужно создать подкласс и реализовать этот метод, который мы увидим ниже. Обратите внимание, что здесь нам не нужно классифицировать метод «init».

  >>> class  Dog (Animal): 
def speak (self):
return self.name + "говорит Гав!" >>> class Cat (Animal):
def speak (self):
return self.name + "Говорит Мяу!" >>> jim = Dog ("Джим")
> >> tim = Кот ("Тим") >>> print (jim.Speak ())
Джим говорит Гав! >>> print (tim.speak ())
Тим говорит Мяу!

Обращайтесь к ноутбуку здесь.

Книги для начинающих, чтобы изучить Python:

Книги для продвинутого уровня, чтобы обратиться к Python:

Обратитесь ко мне: LinkedIn

Ознакомьтесь с моими другими работами: GitHub

Учебник по Python в ООП: наследование

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

Введение и определения

Ни один объектно-ориентированный язык программирования не был бы достоин внимания или использования, если бы он не поддерживал наследование.Наследование было изобретено в 1969 году для Simula. Python поддерживает не только наследование, но и множественное наследование. Вообще говоря, наследование — это механизм наследования новых классов из существующих. Делая это, мы получаем иерархию классов. В большинстве объектно-ориентированных языков на основе классов объект, созданный посредством наследования («дочерний объект»), получает все — хотя в некоторых языках программирования есть исключения — свойств и поведения родительского объекта.

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

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

Синтаксис наследования в Python

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

  класс DerivedClassName (BaseClassName):
    перевал  

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

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

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

Что такое наследование?

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

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

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

Вы можете создать функцию или класс под названием «Move Robot», который управляет движением робота. И вы можете создать метод и функции в других программах, которые могут наследовать «Move Robot». Робот », не переписывая коды снова и снова. Вы также можете расширить этот класс, унаследовав его и написав для него еще несколько кодов, которые будут указывать роботу двигаться, а также запускаться в некоторых конкретных обстоятельствах с использованием операторов if и else.С помощью наследования вы можете создать несколько роботов, которые унаследуют атрибуты родительского класса «Move Robot», что обеспечивает возможность повторного использования кода. ребенок. Ребенок может родиться с некоторыми атрибутами родителей. Наследование обеспечивает возможность повторного использования кодов точно так же, как несколько детей могут наследовать атрибуты своих родителей.

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

_________________________________________________________________________________________________________________________________

Вы работаете в операциях?
Вы знаете, что такое приложение, как NerdVision, может улучшить MTTR (среднее время ремонта)?

_________________________________________________________________________________________________________________________________

Encapsulation

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

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

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

Полиморфизм

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

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

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

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

A создает улитку, которая наследует класс движения, но улитка будет ползать.

B создает кенгуру, который наследует класс движения, но кенгуру будет ползать. leap

C создает Dog, который наследует класс движения, но собаки будут ходить

D создает Fish, который наследует класс движения, но Fish будет плавать.

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

_________________________________________________________________________________________________________________________________

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

_________________________________________________________________________________________________________________________________

Абстракция

Абстракция в Java и Python — это методология программирования, в которой детали программного кода скрыты от пользователю, и пользователю отображается только самое необходимое.Абстракция связана с идеями, а не с событиями. Это похоже на то, что пользователь запускает программу (веб-браузер), но не видит фоновых кодов. Абстракция достигается либо в абстрактных классах, либо в интерфейсе в Java и Python. IDE NetBeans и Eclipse реализует абстракцию для Java, а Django реализует абстракцию для Python.

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

Заключение

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

_________________________________________________________________________________________________________________________________

Команды ИТ-инженеров сокращают MTTR с помощью NerdVision.
Ведущее приложение для отладки, которое каскадно улучшает бизнес

_________________________________________________________________________________________________________________________________

Руководство по ООП Python — Real Python

Все в Python является объектом.Модули — это объекты, определения классов и функции — это объекты, и, конечно же, объекты, созданные из классов, тоже являются объектами.

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

Когда вы пишете код Python с использованием классов, вы используете наследование, даже если вы не знаете, что используете его. Давайте посмотрим, что это значит.

Объект Суперкласс

Самый простой способ увидеть наследование в Python — это перейти в интерактивную оболочку Python и написать небольшой фрагмент кода. Вы начнете с написания простейшего возможного класса:

>>>

  >>> класс MyClass:
...     проходить
...
  

Вы объявили класс MyClass , который мало что делает, но он иллюстрирует самые основные концепции наследования. Теперь, когда у вас объявлен класс, вы можете использовать функцию dir () для вывода списка его членов:

>>>

  >>> c = MyClass ()
>>> dir (c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
  

dir () возвращает список всех членов в указанном объекте.Вы не указали участников в MyClass , так откуда берется список? Вы можете узнать с помощью интерактивного переводчика:

>>>

  >>> o = object ()
>>> dir (o)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__']
  

Как видите, два списка почти идентичны.В MyClass есть несколько дополнительных членов, например __dict__ и __weakref__ , но каждый отдельный член класса объекта также присутствует в MyClass .

Это связано с тем, что каждый класс, который вы создаете в Python, неявно является производным от объекта . Вы могли бы быть более явным и написать class MyClass (object): , но это избыточно и ненужно.

Примечание: В Python 2 необходимо явно наследовать от объект по причинам, выходящим за рамки данной статьи, но вы можете прочитать об этом в разделе «Классические классы нового стиля» документации Python 2.

Исключения являются исключением

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

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

>>>

  >>> класс MyError:
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: исключения должны быть производными от BaseException
  

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

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

Правильный способ определить тип ошибки следующий:

>>>

  >>> класс MyError (Исключение):
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
__main __. MyError
  

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

Создание иерархии классов

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

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

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

Вы начинаете с реализации класса PayrollSystem , который обрабатывает расчет заработной платы:

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')
  

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

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

  # В часах в год

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя
  

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

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

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

  # В час.ру

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary
  

Вы создаете производный класс SalaryEmployee , который наследует Employee . Класс инициализируется идентификатором id и именем , требуемым базовым классом, и вы используете super () для инициализации членов базового класса.Вы можете прочитать все о super () в Supercharge Your Classes With Python super ().

SalaryEmployee также требуется параметр инициализации weekly_salary , который представляет сумму, которую сотрудник зарабатывает в неделю.

Класс предоставляет требуемый метод .calculate_payroll () , используемый системой HR. Реализация просто возвращает сумму, хранящуюся в weekly_salary .

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

  # В час.ру

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate
  

Класс HourlyEmployee инициализируется идентификатором id и именем , как и базовый класс, плюс hours_worked и hour_rate , необходимых для расчета заработной платы.Метод .calculate_payroll () реализуется путем возврата количества отработанных часов, умноженного на почасовую ставку.

Наконец, в компании работают торговые партнеры, которым выплачивается фиксированная заработная плата плюс комиссионные, основанные на их продажах, поэтому вы создаете класс CommissionEmployee class:

  # В часах в год

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        себя.комиссия = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Вы выводите CommissionEmployee из SalaryEmployee , потому что оба класса должны учитывать weekly_salary . В то же время CommissionEmployee инициализируется значением комиссии , которое основано на продажах для сотрудника.

.calculate_payroll () использует реализацию базового класса для получения фиксированной зарплаты и добавляет значение комиссии.

Поскольку CommissionEmployee является производным от SalaryEmployee , у вас есть доступ напрямую к свойству weekly_salary , и вы могли бы реализовать .calculate_payroll () , используя значение этого свойства.

Проблема с прямым доступом к свойству заключается в том, что если реализация SalaryEmployee.calculate_payroll () изменится, вам также придется изменить реализацию CommissionEmployee.calculate_payroll () .Лучше полагаться на уже реализованный метод в базовом классе и расширять функциональность по мере необходимости.

Вы создали свою иерархию первого класса для системы. UML-диаграмма классов выглядит так:

На схеме показана иерархия наследования классов. Производные классы реализуют интерфейс IPayrollCalculator , который требуется для PayrollSystem . Реализация PayrollSystem.calculate_payroll () требует, чтобы переданные объекты employee содержали id , name и реализацию calculate_payroll () .

Интерфейсы представлены аналогично классам со словом interface над именем интерфейса. Имена интерфейсов обычно начинаются с заглавной буквы I .

Приложение создает своих сотрудников и передает их в систему расчета заработной платы для обработки расчета заработной платы:

  # В program.py

импортные часы

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = hr.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = час.Система начисления заработной платы()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

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

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

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

Обратите внимание, что базовый класс Employee не определяет метод .calculate_payroll () . Это означает, что если вы создадите простой объект Employee и передадите его в PayrollSystem , то получите ошибку. Вы можете попробовать это в интерактивном интерпретаторе Python:

>>>

  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, «Недействительный»)
>>> payroll_system = hr.PayrollSystem ()
>>> payroll_system.calculate_payroll ([сотрудник])

Заработная плата для: 1 - недействительна
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
  Файл "/hr.py", строка 39, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
AttributeError: объект «Сотрудник» не имеет атрибута «calculate_payroll»
  

Хотя вы можете создать экземпляр объекта Employee , этот объект не может использоваться системой PayrollSystem .Почему? Потому что не может .calculate_payroll () для Employee . Чтобы соответствовать требованиям PayrollSystem , вам нужно преобразовать класс Employee , который в настоящее время является конкретным классом, в абстрактный класс. Таким образом, ни один сотрудник не будет просто сотрудником , но будет реализован .calculate_payroll () .

Абстрактные базовые классы в Python

Класс Employee в приведенном выше примере называется абстрактным базовым классом.Абстрактные базовые классы существуют для наследования, но никогда не создаются. Python предоставляет модуль abc для определения абстрактных базовых классов.

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

Модуль abc в стандартной библиотеке Python предоставляет функциональные возможности для предотвращения создания объектов из абстрактных базовых классов.

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

  # В часах в год

from abc import ABC, abstractmethod

класс Сотрудник (ABC):
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

    @abstractmethod
    def calculate_payroll (самостоятельно):
        проходить
  

Вы наследуете Employee от ABC , что делает его абстрактным базовым классом. Затем вы украшаете .Calcul_payroll () с декоратором @abstractmethod .

У этого изменения есть два хороших побочных эффекта:

  1. Вы сообщаете пользователям модуля, что объекты типа Employee создавать нельзя.
  2. Вы говорите другим разработчикам, работающим над модулем hr , что если они являются производными от Employee , то они должны переопределить абстрактный метод .calculate_payroll () .

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

>>>

  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, 'аннотация')

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: не удается создать экземпляр абстрактного класса Employee с помощью абстрактных методов
Calcul_payroll
  

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

Наследование реализации и наследование интерфейса

Когда вы производите один класс от другого, производный класс наследует оба:

  1. Интерфейс базового класса: Производный класс наследует все методы, свойства и атрибуты базового класса.

  2. Реализация базового класса: Производный класс наследует код, реализующий интерфейс класса.

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

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

В Python нет необходимости явно объявлять интерфейс. Любой объект, реализующий желаемый интерфейс, может использоваться вместо другого объекта. Это известно как утка, набирающая . Утиный набор текста обычно объясняется так: «Если он ведет себя как утка, значит, это утка».

Чтобы проиллюстрировать это, теперь вы добавите класс DisgruntledEmployee в приведенный выше пример, который не является производным от Employee :

.

  # В disgruntled.py

класс DisgruntledEmployee:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

    def calculate_payroll (самостоятельно):
        возврат 1000000
  

Класс DisgruntledEmployee не является производным от Employee , но предоставляет тот же интерфейс, который требуется для PayrollSystem . PayrollSystem.calculate_payroll () требует список объектов, реализующих следующий интерфейс:

  • Свойство или атрибут id , который возвращает идентификатор сотрудника
  • name свойство или атрибут, представляющий имя сотрудника
  • А .Calcul_payroll () метод, который не принимает никаких параметров и возвращает сумму заработной платы для обработки

Всем этим требованиям удовлетворяет класс DisgruntledEmployee , поэтому PayrollSystem все еще может рассчитывать свою заработную плату.

Вы можете изменить программу для использования класса DisgruntledEmployee :

  # В program.py

импортные часы
недовольный импорт

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = час.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
disgruntled_employee = disgruntled.DisgruntledEmployee (20000, 'Анонимный')
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee,
    disgruntled_employee
])
  

Программа создает объект DisgruntledEmployee и добавляет его в список, обрабатываемый системой PayrollSystem .Теперь вы можете запустить программу и увидеть ее результат:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Заработная плата для: 20000 - Аноним
- Сумма чека: 1000000
  

Как видите, PayrollSystem все еще может обрабатывать новый объект, потому что он соответствует желаемому интерфейсу.

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

  • Используйте наследование для повторного использования реализации: Производные классы должны использовать большую часть своей реализации базового класса. Они также должны моделировать отношения как . Класс Customer может также иметь идентификатор id и имя , но Customer не является Employee , поэтому не следует использовать наследование.

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

Теперь вы можете очистить приведенный выше пример, чтобы перейти к следующей теме. Вы можете удалить файл disgruntled.py , а затем изменить модуль hr в исходное состояние:

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {сотрудник.Calcul_payroll ()} ')
            Распечатать('')

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Вы удалили импорт модуля abc , поскольку класс Employee не обязательно должен быть абстрактным.Вы также удалили из него абстрактный метод calculate_payroll () , поскольку он не предоставляет никакой реализации.

По сути, вы наследуете реализацию атрибутов id и name класса Employee в производных классах. Поскольку .calculate_payroll () - это просто интерфейс для метода PayrollSystem.calculate_payroll () , вам не нужно реализовывать его в базовом классе Employee .

Обратите внимание, как класс CommissionEmployee является производным от SalaryEmployee . Это означает, что CommissionEmployee наследует реализацию и интерфейс SalaryEmployee . Вы можете увидеть, как метод CommissionEmployee.calculate_payroll () использует реализацию базового класса, поскольку он полагается на результат от super (). Calculate_payroll () для реализации своей собственной версии.

Проблема взрыва класса

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

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

Система ProductivitySystem отслеживает производительность в зависимости от ролей сотрудников. Существуют разные роли сотрудников:

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

С этими требованиями вы начинаете видеть, что Employee и его производные классы могут принадлежать не к модулю hr , а к другому, потому что теперь они также используются ProductivitySystem .

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

  # В employee.py

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        супер().__init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        супер().__init __ (id, name, weekly_salary)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

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

  # В program.py

импортные часы
импортные сотрудники

salary_employee = сотрудники.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = сотрудники.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = employee.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

Вы запускаете программу и проверяете, что она по-прежнему работает:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

Когда все готово, вы начинаете добавлять новые классы:

  # У сотрудников.ру

Менеджер класса (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} кричит и кричит {hours} часов.')

классный секретарь (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит {hours} часов на оформление офисных документов.')

class SalesPerson (CommissionEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит на телефон {hours} часов.')

класс FactoryWorker (HourlyEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} производит гаджеты в течение {часов} часов.')
  

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

Затем вы добавляете Secret , SalesPerson и FactoryWorker , а затем реализуете интерфейс work () , чтобы их можно было использовать в системе повышения производительности.

Теперь вы можете добавить класс ProductivitySytem :

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            employee.work (часы)
        Распечатать('')
  

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

  # В program.py

импортные часы
импортные сотрудники
производительность импорта

менеджер = сотрудники.Менеджер (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = employee.FactoryWorker (2, 'Джейн Доу', 40, 15)
сотрудники = [
    менеджер,
    секретарь,
    sales_guy,
    рабочий,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (сотрудников, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (сотрудники)
  

Программа формирует список сотрудников разного типа.Список сотрудников отправляется в систему продуктивности для отслеживания их работы в течение 40 часов. Затем тот же список сотрудников отправляется в систему расчета заработной платы для расчета их заработной платы.

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

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600
  

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

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

На следующей диаграмме показана новая иерархия классов:

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

Наследование нескольких классов

Python - один из немногих современных языков программирования, поддерживающих множественное наследование.Множественное наследование - это возможность одновременно наследовать класс из нескольких базовых классов.

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

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

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

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

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

  1. Унаследовать от Секретарь : Вы можете унаследовать от Секретарь , чтобы унаследовать .work () для роли, а затем переопределите метод .calculate_payroll () , чтобы реализовать его как HourlyEmployee .

  2. Производный от HourlyEmployee : Вы можете наследовать от HourlyEmployee метод .calculate_payroll () , а затем переопределить метод .work () , чтобы реализовать его как секретарь .

Затем вы помните, что Python поддерживает множественное наследование, поэтому вы решаете наследовать как от Secret , так и от HourlyEmployee :

  # У сотрудников.ру

class TemporarySecretary (Секретарь, Почасовой сотрудник):
    проходить
  

Python позволяет наследовать два разных класса, указав их в скобках в объявлении класса.

Теперь вы измените свою программу, добавив нового временного секретаря:

  импортных часов
импортные сотрудники
производительность импорта

manager = employee.Manager (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = сотрудники.FactoryWorker (4, 'Джейн Доу', 40, 15)
временный_секретарий = сотрудники.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
company_employees = [
    менеджер,
    секретарь,
    sales_guy,
    рабочий,
    временный_секретарь,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (company_employees, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (company_employees)
  

Вы запускаете программу для проверки:

  $ программа на Python.ру

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
TypeError: __init __ () принимает 4 позиционных аргумента, но было дано 5
  

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

Это связано с тем, что вы получили TemporarySecretary сначала из секретарь , а затем из HourlyEmployee , поэтому переводчик пытается использовать секретарь.__init __ () для инициализации объекта.

Ладно, перевернем:

  класс Временный секретарь (почасовый сотрудник, секретарь):
    проходить
  

Теперь запустите программу еще раз и посмотрите, что произойдет:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

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

Может быть, реализация TemporarySecretary .__ init __ () поможет:

  # В employee.py

class TemporarySecretary (Почасовой сотрудник, секретарь):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
  

Попробуйте:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "Сотрудник".py ", строка 54, в __init__
  super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

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

Когда осуществляется доступ к методу или атрибуту класса, Python использует класс MRO, чтобы найти его. MRO также используется super () , чтобы определить, какой метод или атрибут вызывать.Вы можете узнать больше о super () в Supercharge Your Classes With Python super ().

Вы можете оценить MRO класса TemporarySecretary с помощью интерактивного интерпретатора:

>>>

  >>> из сотрудников ввозят временного секретаря
>>> Временный секретарь .__ mro__

(<класс 'employee.TemporarySecretary'>,
 <класс 'employee.HourlyEmployee'>,
 <класс 'сотрудники. Секретарь'>,
 <класс 'employee.SalaryEmployee'>,
 <класс сотрудников.Сотрудник '>,
 <класс 'объект'>
)
  

MRO показывает порядок, в котором Python будет искать соответствующий атрибут или метод. В этом примере это происходит, когда мы создаем объект TemporarySecretary :

  1. Вызывается метод TemporarySecretary .__ init __ (self, id, name, hours_worked, hour_rate) .

  2. Вызов super () .__ init __ (id, name, hours_worked, hour_rate) соответствует HourlyEmployee.__init __ (self, id, name, hour_worked, hour_rate) .

  3. HourlyEmployee вызывает super () .__ init __ (id, name) , который MRO собирается сопоставить с Secret .__ init __ () , который унаследован от SalaryEmployee .__ init __ (self, id, name, weekly_salary ) .

Поскольку параметры не совпадают, возникает исключение TypeError .

Вы можете обойти MRO, изменив порядок наследования и напрямую позвонив по номеру HourlyEmployee.__init __ () следующим образом:

  класс TemporarySecretary (секретарь, почасовый сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)
  

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

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
Отслеживание (последний вызов последний):
  Файл ".\ program.py ", строка 20, в 
    payroll_system.calculate_payroll (сотрудники)
  Файл "hr.py", строка 7, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
  Файл employee.py, строка 12, в файле calculate_payroll
    return self.weekly_salary
AttributeError: объект TemporarySecretary не имеет атрибута weekly_salary
  

Проблема в том, что, поскольку вы изменили порядок наследования, MRO находит метод .calculate_payroll () для SalariedEmployee перед методом в HourlyEmployee .Вам нужно переопределить .calculate_payroll () в TemporarySecretary и вызвать из него правильную реализацию:

  класс TemporarySecretary (секретарь, почасовый сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)

    def calculate_payroll (самостоятельно):
        return HourlyEmployee.calculate_payroll (self)
  

Метод calculate_payroll () напрямую вызывает HourlyEmployee.Calcul_payroll () , чтобы убедиться, что вы получите правильный результат. Вы можете снова запустить программу, чтобы убедиться, что она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

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

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

На следующей диаграмме показана проблема ромба в иерархии классов:

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

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

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

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

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

  1. Система продуктивности , отслеживающая продуктивность сотрудников.

  2. Система расчета заработной платы , которая рассчитывает заработную плату сотрудников.

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

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            result = employee.work (часы)
            print (f '{имя сотрудника}: {результат}')
        Распечатать('')

класс ManagerRole:
    def работа (самостоятельно, часы):
        ответь f'scream и кричит в течение {hours} часов ».

класс Секретарь
    def работа (самостоятельно, часы):
        return f'expends {hours} часов на оформление офисных документов.'

class SalesRole:
    def работа (самостоятельно, часы):
        return f 'тратит {hours} часов на телефон.'

класс FactoryRole:
    def работа (самостоятельно, часы):
        вернуть гаджеты на {hours} часов ».
  

Модуль продуктивности реализует класс ProductivitySystem , а также связанные роли, которые он поддерживает. Классы реализуют интерфейс work () , необходимый для системы, но они не являются производными от Employee .

То же самое можно сделать с модулем hr :

  # В час.ру

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')

класс SalaryPolicy:
    def __init __ (self, weekly_salary):
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

класс HourlyPolicy:
    def __init __ (self, hours_worked, hour_rate):
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

класс CommissionPolicy (SalaryPolicy):
    def __init __ (self, weekly_salary, Commission):
        super () .__ init __ (недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Модуль hr реализует систему PayrollSystem , которая рассчитывает заработную плату для сотрудников.Он также реализует классы политики для расчета заработной платы. Как видите, классы политик больше не являются производными от Employee .

Теперь вы можете добавить необходимые классы в модуль employee :

  # В employee.py

из часов импорта (
    SalaryPolicy,
    CommissionPolicy,
    Почасовая политика
)
из импорта производительности (
    ManagerRole,
    Секретарь роль,
    SalesRole,
    FactoryRole
)

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

Менеджер класса (Сотрудник, ManagerRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

класс Секретарь (Employee, SecretRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

class SalesPerson (Сотрудник, SalesRole, CommissionPolicy):
    def __init __ (self, id, name, weekly_salary, Commission):
        CommissionPolicy.__init __ (самостоятельно, недельная_ зарплата, комиссия)
        super () .__ init __ (идентификатор, имя)

класс FactoryWorker (Сотрудник, FactoryRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)

класс TemporarySecretary (Сотрудник, SecretRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)
  

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

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

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

Вот диаграмма UML для нового дизайна:

На схеме показаны отношения для определения Секретарь и TemporarySecretary с использованием множественного наследования, но избегая проблемы ромба.

Вы можете запустить программу и посмотреть, как она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс: кричит и кричит 40 часов.Джон Смит: тратит 40 часов на оформление офисных документов.
Кевин Бэкон: 40 часов разговаривает по телефону.
Джейн Доу: производит гаджеты 40 часов.
Робин Уильямс: тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

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

ООП - Наследование - CCM

Последнее обновление Пятница 15 января 2021 в 04:41 , Елена Керачева.

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

Иерархия классов

Отношения «родитель-потомок» между классами могут быть представлены в иерархическом представлении, часто называемом древовидным представлением классов . Древовидное представление классов начинается с общего класса, называемого суперклассом (иногда называемого базовым классом , родительским классом , классом предков , родительским классом или родительским классом ), существует много генеалогических метафор).Производные классы ( дочерний класс или подкласс ) становятся более специализированными в дальнейшем по дереву. Поэтому мы обычно ссылаемся на отношение, которое связывает один дочерний класс с родительским классом, фразой « is a (n) » x of y).

Множественное наследование

Некоторые объектно-ориентированные языки, такие как C ++ , допускают множественное наследование
, что означает, что один класс может наследовать атрибуты от двух суперклассов. Этот метод можно использовать для группировки атрибутов и методов из нескольких классов в один класс.

Преимущества

Есть два основных преимущества наследования.

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

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

Изображение: Unsplash

Как кодировать наследование в Java - Учебное пособие по ООП для начинающих | Риши Сидху

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

Давайте разберемся с концепцией «наследования» в объектно-ориентированном программировании. прошлые поколения.

В объектно-ориентированном дизайне наследование имеет аналогичный (не совсем такой же) оттенок. Наследование означает

  1. Организация классов в иерархии
  2. Более высокая иерархия наследует свойства от более низкой иерархии
  3. Объединение похожих вещей в один и тот же класс
  4. Классы переходят от общих к частным по мере продвижения вверх по иерархии

Как выглядит иерархия | Иконки, сделанные Pixel perfect с сайта www.flaticon.com

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

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

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

Есть / есть Связь между иерархией

По мере того, как мы поднимаемся выше в иерархия мы становимся все более и более конкретной.

Иерархия и универсальность

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

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

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

Класс - Группа похожих объектов
Поля - Свойства класса
Объекты - Экземпляры класса
Методы - Действия, которые могут применяться к объектам
Конструктор - Действие, которое используется для создания объекта

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

Источник значка

  • Поля книги - Имя автора, себестоимость, продажная цена, название и страницы
  • Методы книги - netProfit () и getTax ()
  • Поля EBook - downloadSite and sizeMB
  • EBook методы - pageSize (), printTitle () и getTax ()

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

Теперь обратите внимание на то, что электронная книга может не только получить доступ к 2 методам класса Book, а именно netProfit () и getTax (), но также может их изменять. Обратите внимание на изменение расчета налога в приведенном ниже коде. В приведенном ниже коде обратите внимание на то, как EBook наследуется от Book с помощью ключевого слова extends.

  • Дополнение - Класс EBook расширяет класс Book, добавляя поля downloadSite и sizeMB, а также добавляя метод pageSize ().Обратите внимание, что downloadSite не имеет отношения к PaperBooks, поэтому его нельзя поместить в класс Book. Кроме того, pageSize () измеряется в МБ, поэтому он характерен для электронных книг.
  • Переопределение - с электронных книг взимается дополнительный налог в размере 2 долларов, помимо 30% налога на прибыль.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *