Меню
Видеоучебник

Принципы ООП: инкапсуляция

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

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

Конспект урока "Принципы ООП: инкапсуляция"

Вопросы:

·     Определение инкапсуляции.

·     Использование инкапсуляции в программировании.

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

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

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

Положение круга в пространстве можно задать координатами точки его центра. Для описания этой точки опишем отдельный класс, который так и назовём – «Точка». У этого класса будет два поля – его координаты x и y, заданные вещественными числами.

Опишем наш круг. Начнём с описания класса Point, что в переводе означает «точка». Опишем конструктор этого класса. Очевидно, что у объекта этого класса будет 2 поля – его координаты x и y. По умолчанию будем размещать точку в начале отсчёта, то есть сделаем её поля равными нулю.

class Point:

    def __init__ (self):

        self.x = 0

        self.y = 0

Далее опишем класс Сircle, что в переводе означает «круг». Опишем конструктор этого класса. У круга будет 3 поля: радиус, который обозначим буквой R, по умолчанию он будет равен нулю; цвет – Calor, который по умолчанию будет чёрным, то есть будет описываться символьной строкой: 000000; и центр, который будет объектом класса Point. Мы закончили описание класса Circle.

class Circle:

    def __init__ (self):

        self.R = 0

        self.Colour = '000000'

        self.Center = Point ()

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

class Circle:

    def __init__ (self):

        self.__R = 0

        self.__Colour = '000000'

        self.Center = Point ()

Попробуем в основной программе вывести на экран значение поля __R у объекта c. Сохраним описанный модуль и запустим его на выполнение. Программа вывела сообщение о том, что у объектов класса Circle нет таких атрибутов.

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

Опишем эти методы для поля __R. Начнём с метода getR. В этом методе достаточно написать инструкцию возврата значения соответствующего поля. Этот метод общедоступен, поэтому его можно вызвать в любом месте программы, и при этом он описан в классе Circle, а значит имеет доступ ко всем его полям. Теперь опишем метод для изменения радиуса круга setR. Помимо обычного параметра self у него будет ещё один параметр, назовём его value, что переводится как «значение». В этом методе просто присвоим внутреннему полю __R значение параметра value.

def __getR (self):

        return self.__R

 

def __setR (self, value):

        self.__R = value

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

И здесь у вас мог возникнуть вопрос: «А в чём смысл того, что мы сделали?» Ведь несмотря на то, что поле __R стало внутренним, мы всё равно можем изменять его значение так, как захотим с помощью методов доступа. Дело в том, что мы можем описать любую логику работы методов доступа, какую только захотим. Например, мы можем изменить метод setR, добавив в него защиту от некорректных данных. Вначале опишем в методе обработчик исключений, в нём запишем ветвление с условием, что результат функции float (value) >= 0. Если это условие выполняется, то присвоим полю R это значение. Если условие ветвления ложно или же этот код вернёт любое исключение, то ничего делать не будем.

def __setR (self, value):

    try:

        if float (value) >= 0:

            self.__R = float (value)

        except:

            pass

Таким образом метод setR изменит значение поля R лишь в том случае, если его параметром будет неотрицательное число, в противном случае ничего не произойдёт. Проверим это. Уберём описание основной программы, оставив в модуле только описанные классы. Сохраним изменённый модуль и запустим его на выполнение. В интерактивном режиме среды разработки создадим объект c класса Circle, с помощью метода setR попробуем сделать его радиус отрицательным. Теперь просмотрим значение радиуса окружности c, вызвав у неё метод getR. Радиус окружности всё ещё равен нулю. Теперь попробуем сделать радиус c равным 7.5 и снова просмотрим его значение. На этот раз оно изменилось. Теперь круг c не принимает некорректный радиус.

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

Посмотрим, как это работает. Для этого в классе Circle за пределами методов объявим переменную с именем R без подчёркиваний и присвоим ей некоторое значение. После знака равенства запишем слово property, что в переводе означает «cвойство», и после него в скобках, через запятую, укажем методы для чтения и записи значения этого свойства, то есть getR и setR.

R = property (__getR, __setR)

Сохраним изменения в модуле и запустим его на выполнение. Снова создадим объект c класса Circle. Изменим его радиус, обратившись к полю R. Попытаемся присвоить ему значение произвольной символьной строки. Теперь просмотрим его значение. Радиус всё ещё начальный, то есть равен нулю. Теперь попробуем присвоить ему значение 5, и снова просмотрим его. Теперь радиус изменился. Интерфейс обращения к полю радиуса стал прежним.

Теперь мы можем сделать закрытыми методы доступа к радиусу окружности. Они будут доступны из открытого свойства R. Теперь изменим доступ к остальным полям класса. Для изменения поля Color опишем внутренний метод __setColor с параметром value. В нём проверим, действительно ли value содержит шестизначное шестнадцатеричное число. Для этого запишем обработчик исключений, в котором запишем ветвление с условием, что длина символьной строки value равна 6. Если это условие выполняется, попробуем полю __Color присвоить значение функции int (value, 16), в которой в качестве второго параметра указано основание системы счисления. Эта функция преобразует значение в шестнадцатеричное целое число. Если при попытке преобразования возникнет исключение, то ничего делать не будем. Опишем внутренний метод __getColor, который будет возвращать значение внутреннего поля __Color, преобразованного в символьную строку с помощью метода format. Строка формата для этого метода будет содержать символы: {:6x}. Она означает, что нужно вывести шестнадцатеричное число в шесть знаковых позиций, причём свободные позиции слева нужно заполнить нулями. Теперь опишем общедоступное свойство Color с методами доступа __getColor и __setColor.

def __setColour (self, value):

    try:

        if len (str (value)) == 6:

            self.__Colour = int (value, 16)

    except:

        pass

 

def __getColour (self):

    return '{:06x}'.format (self.__Colour)

 

Colour = property (__getColour, __setColour)

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

class Point:

    def __init__ (self):

        self.__x = 0

        self.__x = 0

 

    def __getX (self):

        return self.__x

 

    def __setX (self, value):

        try:

            self.__x = float (value)

        except:

            pass

 

    def __getY (self):

        return self.__x

 

    def __setY (self, value):

        try:

            self.__x = float (value)

        except:

            pass

 

    x = property (__getX, __setX)

    y = property (__getY, __setY)

Таким образом мы получили классы Point и Circle. В них предусмотрена защита от некорректных данных, которые могут поступить извне. Проверим это. Сохраним модуль и запустим его на выполнение. Создадим объект c класса Circle. Теперь попробуем у созданного круга изменить координату x его центра на произвольную символьную строку, после чего просмотреть её. Координата не изменилась. Попробуем присвоить координате y центра значение 5 и просмотреть её. Значение координаты было успешно изменено. Теперь попробуем изменить цвет круга на красный. Его код: FF0000. Теперь проверим значение цвета. Он действительно красный. Попробуем задать в качестве цвета произвольную символьную строку, после чего проверим его значение. Цвет всё ещё красный. Описанные нами классы работают правильно, хотя при этом их размер значительно увеличился по сравнению с начальным.

Мы узнали:

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

·     Интерфейсом класса называется система его внешних полей и методов.

·     Свойство – это способ доступа к внутреннему состоянию объекта, имитирующий обращение к его внутреннему полю.

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

0
1443

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

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