Меню
Видеоучебник
Видеоучебник  /  Информатика  /  Основы алгоритмизации и программирования на языке Python  /  Принципы ООП: наследование и полиморфизм

Принципы ООП: наследование и полиморфизм

Урок 26. Основы алгоритмизации и программирования на языке Python

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

Конспект урока "Принципы ООП: наследование и полиморфизм"

Вопросы:

·     Определения наследования и полиморфизма.

·     Наследование и полиморфизм в программировании.

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

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

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

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

Рассмотрим наследование на практике. Ранее мы реализовали классы, которые описывают точку и цветной круг на плоскости – Point и Circle. Мы знаем, что у класса Point есть поля x и y, которые содержат соответствующие координаты точки, а у класса Circle – поля: R - радиус круга, Color - цвет круга и Center - точка центра круга. Создадим на основе класса Circle в другом модуле новый класс, который описывает цветной круг, движущийся на плоскости с постоянной скоростью и направлением.

Из условия задачи можно сделать вывод о том, что мы должны создать новый класс, назовём его MovCircle, в котором будут те же поля, что и в описанном ранее классе Circle, а также метод, который будет отвечать за движение круга. Назовём его Move. Для того, чтобы было понятно, в каком направлении и с какой скоростью будет двигаться круг, добавим в класс поле __V, в котором будут храниться координаты вектора скорости. Для изменения вектора скорости добавим в класс метод ChangeV с двумя параметрами, которые будут задавать новые координаты вектора скорости.

Начнём описание модуля. Мы описали классы Circle и Point в модуле с именем Circle. Загрузим их. Теперь начнём описание класса MovCircle. Имена предков класса указываются в скобках после его имени. У описываемого класса один предок – класс Circle. Теперь опишем метод-конструктор нового класса. Так как большая часть полей нового класса будет взята у его предка, то мы можем в этом методе сначала вызвать конструктор класса Circle, который мы описали ранее. Для этого вызывается метод с именем super без параметров. Он возвращает ссылку на класс-предок. У этого класса вызывается соответствующий метод, в нашем случае это метод __init__ без параметров. Таким образом в классе MovCircle мы определим все поля класса Circle. Однако, помимо этих полей, мы должны создать в классе ещё одно внутреннее поле V, которое будет содержать объект класса Point.

from Circle import Point, Circle

 

class MovCircle (Circle):

    def __init__ (self):

        super ().__init__()

        self.__V = Point ()

Теперь опишем метод ChangeV. У него, помимо обычного параметра self, будет ещё 2 параметра, назовём их x и y. Опишем в методе обработчик исключений. В нём, в блоке try попробуем преобразовать значения параметров x и y в вещественный тип float и присвоить значения этих параметров соответствующим полям вектора скорости. Если же в процессе исполнения этого кода возникло исключение, то ничего делать не будем.

def changeV (self, x, y):

    try:

        x = float (x)

        y = float (y)

        self.__V.X = x

        self.__V.Y = y

    except:

        pass

Чтобы можно было просмотреть значение вектора скорости, опишем внутренний метод __getV, в котором будем возвращать значения координат поля __V. Теперь опишем общедоступное свойство V, в котором укажем только метод для чтения - __getV.

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

def __getV (self):

        return self.__V.X, self.__V.Y

 

    V = property (__getV)

 

    def Move (self):

        self.Center.x = self.Center.x + self.__V.x

        self.Center.y = self.Center.y + self.__V.y

Сохраним описанный модуль в одной папке с модулем Circle, описанным ранее, и запустим его на выполнение. Создадим в переменной c объект класса MovCircle. Сейчас координаты центра круга c находятся в начале отсчёта. Мы можем проверить это, просмотрев значения соответствующих полей. Теперь зададим вектор скорости движения круга с координатами 3 и 5, после чего вызовем у круга c метод move. Теперь снова проверим координаты центра круга, они увеличились на координаты вектора его скорости. Теперь снова вызовем метод move и просмотрим координаты центра круга. Они снова увеличились на координаты вектора скорости. Таким образом мы получили новый класс, в котором полностью реализована функциональность класса Circle, а также своя дополнительная функциональность. Задача решена.

Рассмотрим ещё один пример. Опишем класс «Домашнее животное» с полем «Имя» и методом, при вызове которого домашнее животное будет выполнять команду «Голос», а также два его класса-наследника – «Cобака» и «Кошка». Начнём с описания класса «Домашнее животное». На английский язык словосочетание «Домашнее животное» можно перевести одним словом – Pet. Так и назовём класс. Опишем его конструктор. Помимо обычного параметра self, у него будет ещё один строковый параметр s, через который будем задавать кличку домашнего животного. В самом конструкторе опишем общедоступное поле Name, в котором будем хранить кличку животного, и присвоим этому полю значение параметра s. Описание конструктора класса завершено.

class Pet:

    def __init__ (self, s):

        self.Name = s

Теперь опишем метод Speak, при вызове которого домашнее животное будет выполнять команду «Голос». Однако при описании этого метода у нас возникает проблема… Дело в том, что этот класс не уточняет, какое именно у нас домашнее животное: собака, кошка или какое-то другое, соответственно, мы не знаем, что должно делать это домашнее животное по команде «Голос»: лаять, мяукать или мычать. Поэтому мы не будем описывать работу метода Speak в этом классе, а опишем его позже для его наследников.

def Speak (self):

        pass

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

Это означает, что в программе не могут создаваться объекты класса Pet, этот класс будет использован лишь для наследования от него других классов. Однако сейчас мы можем беспрепятственно создавать объекты этого класса. Проблемы возникнут, когда мы попытаемся вызвать у одного из этих объектов метод Speak, ведь он ничего не делает. Мы должны запретить создание объектов класса Pet, для этого мы обозначим этот класс как абстрактный. Инструменты для этого описаны в модуле abc, это аббревиатура от английского «abstract base class». Подключим этот модуль за пределами класса. Теперь, чтобы обозначить класс Pet как абстрактный, нужно при его объявлении, в скобках, параметру metaclass присвоить значение abc.ABCMeta. Также нам нужно обозначить метод Speak этого класса как абстрактный. Для этого перед описанием этого метода запишем строку @abc.abstractmethod. Это строка-декоратор, которая означает, что метод, который описывается после неё, является абстрактным. Теперь при попытке создать объект класса Pet будет возвращено исключение типа TypeError.

import abc

 

class Pet (metaclass = abc.ABCMeta):

    def __init__ (self, s):

        self.Name = s

 

    @abc.abstractmethod

    def Speak (self):

        pass

Теперь опишем наследников класса Pet. Начнём с класса Dog, его предком будет класс Pet. Конструктор класса Dog будет работать так же, как и у его предка, поэтому мы не будем его описывать. Нам нужно определить в этом классе метод Speak. Так как собака лает, то в этом методе с помощью инструкции print будем выводить на экран текстовую строку «Woof!».

class Dog (Pet):

    def Speak (self):

        print ('Woof!')

Теперь так же опишем класс Cat. В методе Speak его объекты будут выводить на экран текстовую строку «Meow!».

class Cat (Pet):

    def Speak (self):

        print ('Meow!')

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

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

Мы узнали:

·     В объектно-ориентированном программировании программа представляет собой множество взаимодействующих объектов. При этом каждый объект – это экземпляр определённого класса, а классы образуют иерархию наследования.

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

·     Полиморфизм – это возможность классов-наследников по-разному реализовывать методы своего предка.

0
1903

Комментарии 0

Чтобы добавить комментарий зарегистрируйтесь или на сайт