3. Процедурное и объектно-ориентированное программирование

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

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

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

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

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

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

В отличие от процедурного программирования, в центре внимания которого находится создание процедур (функций), объектно-ориентированное программирование (ООП) сосредоточено на создании объектов.

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

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

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

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

Классы и объекты

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

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

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

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

Определения классов

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

import random

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

class Coin:

    # Метод __init__ инициализирует
    # атрибут данных sideup значением 'Орел'

    def __init__(self):
        self.sideup = "Орел"

    # Метод toss генерирует случайное число
    # в диапазоне от 0 до 1. Если это число
    # равно 0,то sideup получает значение 'Орел'.
    # В противном случае sideup получает значение 'Решка'.

    def toss(self):
        if random.randint(0, 1) == 0:
            self.sideup = "Орел"
        else:
            self.sideup = "Решка"

    # Метод get_sideup возвращает значение,
    # на которое ссылается sideup.

    def get_sideup(self):
        return self.sideup

В строке 1 мы импортируем модуль random, так как для генерации случайного числа мы применяем функцию randint(). Строка 6 является началом определения класса. Оно начинается с ключевого слова class, за которым идет имя класса, после чего идет двоеточие (имя класса крайне желательно писать с большой буквы).

Класс Coin имеет три метода:

  • метод __init__();

  • метод toss() (подбрасывать);

  • метод get_sideup() (получить обращенную вверх сторону монеты).

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

Обратите внимание, что каждый метод имеет переменную с именем self.

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

Давайте рассмотрим каждый из этих методов.

Метод __init__()

Большинство классов Python имеет специальный метод __init__(), который автоматически исполняется, когда экземпляр класса создается в оперативной памяти. Метод __init__() обычно называется методом инициализации (или конструктором), потому что он инициализирует атрибуты данных объекта.

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

В результате работы метода __init__() каждый объект, который мы создаем на основе класса Coin, будет первоначально иметь атрибут sideup с заданным значением ''Орел".

Метод __init__() обычно является первым методом в определении класса.

Метод toss()

Метод toss() выглядит следующим образом:

def toss(self):
    if random.randint(0, 1) == 0:
        self.sideup = "Орел"
    else:
        self.sideup = "Решка"

Этот метод тоже имеет необходимую переменную self. При вызове метода toss(), переменная self автоматически будет ссылаться на объект, которым этот метод должен оперировать.

Метод имитирует подбрасывание монеты. В методе генерируется случайное число от 0 до 1. Если число равняется 0, то атрибуту self.sideup присваивается значение "Орел". В противном случае, атрибуту присваивается значение "Решка".

Метод get_sideup()

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

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

import random

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

class Coin:

    # Метод __init__ инициализирует
    # атрибут данных sideup значением 'Орел'

    def __init__(self):
        self.sideup = "Орел"

    # Метод toss генерирует случайное число
    # в диапазоне от 0 до 1. Если это число
    # равно 0,то sideup получает значение 'Орел'.
    # В противном случае sideup получает значение 'Решка'.

    def toss(self):
        if random.randint(0, 1) == 0:
            self.sideup = "Орел"
        else:
            self.sideup = "Решка"

    # Метод get_sideup возвращает значение,
    # на которое ссылается sideup.

    def get_sideup(self):
        return self.sideup

def main():
    # Создаем объект на основе класса Coin
    my_coin1 = Coin()

    #Показываем обращенную вверх сторону монеты
    print("Эта сторона обращена вверх:", my_coin1.get_sideup());

    for i in range(10):
        #Подрасываем монету
        my_coin1.toss()

        #Показываем обращенную вверх сторону монеты
        print("Эта сторона обращена вверх:", my_coin1.get_sideup());

main()

Рассмотрим подробнее выражение

my_coin1 = Coin()

Выражение Coin(), которое расположено справа от оператора = приводит к тому, что:

  • в оперативной памяти создается объект на основе класса Coin;

  • исполняется метод __init__() класса Coin, и параметру self автоматически назначается объект, который был только что создан. В результате атрибуту sideup этого объекта присваивается строковой литерал ''Орел'.

После этого оператор = присваивает только что созданный объект Coin переменной my_coin1.

Выражение

my_coin1.get_sideup()

использует объект, на который ссылается my_coin1. После исполнения метода параметр self будет ссылаться на объект my_coin. В результате метод возвращает строковое значение "Орел".

Несмотря на то, что метод get_sideup() имеет параметрическую переменную self, нам не нужно передавать в него аргумент. Когда этот метод вызывается, Python автоматически передает в первый параметр метода ссылку на вызывающий объект. В результате параметр self автоматически будет ссылаться на объект, которым этот метод должен оперировать.

Инструкция

my_coin1.toss()

использует объект, на который ссылается my_coin1, для вызова метода toss(). Метод генерирует случайное число и использует это число для изменения значения атрибута sideup данного объекта.

Сокрытие атрибутов

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

Однако в классе Coin, который был показан в предыдущем примере, атрибут sideup не является приватным. К нему могут получать непосредственный доступ код вне класса Coin.

def main():
    # Создаем объект на основе класса Coin
    my_coin1 = Coin()
    
    #Показываем обращенную вверх сторону монеты
    print("Эта сторона обращена вверх:", my_coin1.get_sideup());
    
    my_coin1.sideup = "Нарушаем логику работы объекта"

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

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

class Coin:

    # Метод __init__ инициализирует
    # атрибут данных sideup значением 'Орел'

    def __init__(self):
        self.__sideup = "Орел" # Мы скрываем переменную sideup

Следующий код

def main():
    # Создаем объект на основе класса Coin
    my_coin1 = Coin()
    
    #Показываем обращенную вверх сторону монеты
    print("Эта сторона обращена вверх:", my_coin1.get_sideup());
    
    my_coin1.sideup = "Нарушаем логику работы объекта"
    
    #Показываем обращенную вверх сторону монеты
    print("Эта сторона обращена вверх:", my_coin1.get_sideup());

Будет выполнен следующим образом

Эта сторона обращена вверх: Орел
Эта сторона обращена вверх: Орел

Геттеры и сеттеры

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

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

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

Last updated