Главная страница
Навигация по странице:

  • ООП и наследование: взаимосвязи типа «является»

  • ООП и композиция: взаимосвязи типа «имеет»

  • Придется держать в уме: классы и их хранение

  • Математический анализ. 3е издание


    Скачать 4.86 Mb.
    Название3е издание
    АнкорМатематический анализ
    Дата04.02.2022
    Размер4.86 Mb.
    Формат файлаpdf
    Имя файлаpython_01.pdf
    ТипДокументы
    #351981
    страница78 из 98
    1   ...   74   75   76   77   78   79   80   81   ...   98
    639
    class C:
    def meth(self, x):
    x.operation() # Предполагается, что x работает правильно
    Кроме того, считается, что лучше выбирать разные имена для методов,
    выполняющих разные операции, и не полагаться на сигнатуры вызова
    (при этом неважно, какой язык программирования вы используете).
    Классы как записи
    В главе 8 демонстрировалось, как использовать словари для хранения записей свойств сущностей в программах. Рассмотрим этот прием бо
    лее подробно. Ниже приводится пример использования записи на ос
    нове словаря, использовавшийся ранее:
    >>> rec = {}
    >>> rec['name'] = 'mel'
    >>> rec['age'] = 40
    >>> rec['job'] = 'trainer/writer'
    >>>
    >>> print rec['name']
    mel
    Этот фрагмент имитирует инструмент, напоминающий записи и струк
    туры в других языках программирования. Однако, как мы видели в главе 23, существует еще множество способов сделать то же самое с по
    мощью классов. Ниже приводится, пожалуй, самый простой из них:
    >>> class rec: pass
    >>> rec.name = 'mel'
    >>> rec.age = 40
    >>> rec.job = 'trainer/writer'
    >>>
    >>> print rec.age
    40
    Этот вариант существенно компактнее, чем эквивалент на базе слова
    ря. Здесь для создания объекта пустого пространства имен использует
    ся пустая инструкция class (обратите внимание на инструкцию pass –
    этого требует синтаксис, так как в данном случае класс не содержит никакой логики, которую необходимо было бы реализовать). Создав пустой класс, мы заполняем его, присваивая значения его атрибутам.
    Этот прием работает, но для каждой отдельной записи придется писать новую инструкцию class. Пожалуй, более удобным будет создавать эк
    земпляры класса всякий раз, когда нам потребуется новая запись:
    >>> class rec: pass
    >>> pers1 = rec()
    >>> pers1.name = 'mel'

    640
    Глава 25. Шаблоны проектирования с классами
    >>> pers1.job = 'trainer'
    >>> pers1.age = 40
    >>>
    >>> pers2 = rec()
    >>> pers2.name = 'dave'
    >>> pers2.job = 'developer'
    >>>
    >>> pers1.name, pers2.name
    ('mel', 'dave')
    Здесь из одного и того же класса были созданы две записи – экземпля
    ры класса начинают свое существование пустыми, как и классы. По
    сле этого производится заполнение записей путем присваивания зна
    чений атрибутам. Однако на этот раз существует два отдельных объек
    та и, соответственно, два разных атрибута name. Фактически у экземп
    ляров одного и того же класса не обязательно должны быть одинаковые наборы имен атрибутов. В данном примере один из экземпляров имеет уникальный атрибут age. Экземпляры класса действительно являются разными пространствами имен: каждый из них имеет свой словарь ат
    рибутов. Обычно экземпляры единообразно наполняются атрибутами в методах класса, тем не менее, они обладают большей гибкостью, чем можно было бы ожидать.
    Наконец, для реализации записи мы могли бы написать более полно
    ценный класс:
    >>> class Person:
    ... def __init__(self, name, job):
    ... self.name = name
    ... self.job = job
    ... def info(self):
    ... return (self.name, self.job)
    >>> mark = Person('ml', 'trainer')
    >>> dave = Person('da', 'developer')
    >>>
    >>> mark.job, dave.info()
    ('trainer', ('da', 'developer'))
    Такая схема также допускает создание множества экземпляров, но на этот раз класс уже не пустой: мы добавили в него логику (методы) ини
    циализации экземпляров на этапе создания и сбора атрибутов в кор
    теж. Конструктор налагает некоторые ограничения целостности, тре
    буя значения для двух атрибутов – name и job.
    Мы могли бы продолжить расширение этой реализации, добавляя ме
    тоды для вычисления зарплаты, разбора имен и т. д. (как это делается,
    описывается в примере в конце главы 24). В конечном итоге мы могли бы связать класс в иерархию, чтобы обеспечить наследование набора существующих методов через процедуру автоматического поиска ат
    рибутов классов, и даже записывать экземпляры класса в файл, чтобы

    ООП и наследование: взаимосвязи типа «является»
    641
    обеспечить их постоянное хранение (подробнее о сохранении объектов рассказывается во врезке «Придется держать в уме: классы и их хра
    нение», ниже в этой книге). Наконец, несмотря на всю гибкость таких типов, как словари, классы позволяют наделять объекты поведением таким способом, который встроенными типами и простыми функция
    ми напрямую не поддерживается.
    ООП и наследование: взаимосвязи
    типа «является»
    Мы уже достаточно подробно исследовали механизм наследования, но мне хотелось бы показать пример того, как может использоваться мо
    дель отношений реального мира. С точки зрения программиста, насле
    дование вступает в игру с момента появления составного имени атри
    бута, при разрешении которого запускается поиск имен в экземплярах,
    в их классах и затем в суперклассах. С точки зрения проектировщика,
    наследование – это способ указать принадлежность к некоторому набо
    ру: класс определяет набор свойств, которые могут быть унаследованы и адаптированы более специализированными наборами (то есть под
    классами).
    Чтобы проиллюстрировать сказанное, давайте вернемся к машине по изготовлению пиццы, о которой мы говорили в начале этой части кни
    ги. Предположим, что мы исследовали альтернативные варианты раз
    вития своей карьеры и решили открыть пиццерию. Первое, что нам предстоит сделать, – это нанять работников для обслуживания клиен
    тов, для приготовления блюд и т. д. Будучи в глубине души инженера
    ми, мы решили сконструировать робота по приготовлению пиццы, но,
    будучи также политически и кибернетически корректными, мы реши
    ли сделать нашего робота полноправным служащим, которому выпла
    чивается заработная плата.
    Наш коллектив работников пиццерии можно определить четырьмя классами из файла примера employees.py. Самый общий класс, Employ
    ee
    , реализует поведение, общее для всех работников, такое как повы
    шение заработной платы (giveRaise) и вывод на экран (__repr__). Суще
    ствует два типа служащих и, соответственно, два подкласса, насле
    дующих класс Employee: Chef (повар) и Server (официант). Оба подкласса переопределяют унаследованный метод work, чтобы обеспечить вывод более специализированных сообщений. Наконец, наш робот по приго
    товлению пиццы моделируется еще более специализированным клас
    сом PizzaRobot, наследующим класс Chef, который в свою очередь на
    следует класс Employee. В терминах ООП мы называем такие взаимоот
    ношения «является»: робот является поваром, а повар – служащим.
    Ниже приводится содержимое файла employees.py:
    class Employee:
    def __init__(self, name, salary=0):

    642
    Глава 25. Шаблоны проектирования с классами self.name = name self.salary = salary def giveRaise(self, percent):
    self.salary = self.salary + (self.salary * percent)
    def work(self):
    print self.name, "does stuff"
    def __repr__(self):
    return "" % (self.name, self.salary)
    class Chef(Employee):
    def __init__(self, name):
    Employee.__init__(self, name, 50000)
    def work(self):
    print self.name, "makes food"
    class Server(Employee):
    def __init__(self, name):
    Employee.__init__(self, name, 40000)
    def work(self):
    print self.name, "interfaces with customer"
    class PizzaRobot(Chef):
    def __init__(self, name):
    Chef.__init__(self, name)
    def work(self):
    print self.name, "makes pizza"
    if __name__ == "__main__":
    bob = PizzaRobot('bob') # Создать робота с именем bob
    print bob # Вызвать унаследованный метод __repr__
    bob.work() # Выполнить действие, зависящее от типа
    bob.giveRaise(0.20) # Увеличить роботу зарплату на 20%
    print bob; print for klass in Employee, Chef, Server, PizzaRobot:
    obj = klass(klass.__name__)
    obj.work()
    Когда выполняется программный код самопроверки, включенный в со
    став модуля, создается робот по приготовлению пиццы с именем bob,
    который наследует атрибуты трех классов: PizzaRobot, Chef и Employee.
    Например, при попытке вывести экземпляр bob вызывается метод Em
    ployee.__repr__
    , а прибавка роботу зарплаты производится методом Em
    ployee.giveRaise
    , потому что этот метод обнаруживается в процессе по
    иска в дереве наследования именно в этом классе:
    C:\python\examples> python employees.py

    bob makes pizza

    Employee does stuff
    Chef makes food
    Server interfaces with customer
    PizzaRobot makes pizza

    ООП и композиция: взаимосвязи типа «имеет»
    643
    В иерархиях классов, подобных этой, обычно можно создавать экземп
    ляры любого класса, а не только того, что находится в самом низу. На
    пример, в коде самопроверки этого модуля в цикле for создаются экзем
    пляры всех четырех классов, каждый из которых работает поразному,
    потому что все они имеют различные методы work. В действительности эти классы пока лишь имитируют объекты реального мира – в текущей реализации метод work просто выводит сообщение, но позднее его мож
    но расширить так, что он будет выполнять настоящую работу.
    ООП и композиция: взаимосвязи типа «имеет»
    Понятие композиции в этой книге было введено в главе 22. С точки зрения программиста, композиция – это прием встраивания других объектов в объектконтейнер и использование их для реализации ме
    тодов контейнера. Для проектировщика композиция – это один из способов представить взаимоотношения в прикладной области. Но вместо того, чтобы определять принадлежность к множеству, при ком
    позиционном подходе все части объединяются в единое целое.
    Кроме того, композиция отражает взаимоотношения между частями,
    которые обычно называются отношениями типа «имеет». В некоторых книгах, посвященных объектноориентированному проектированию,
    композиция называется агрегированием (и различие между терминами состоит в том, что термин «агрегирование» используется для описания более слабой зависимости между контейнером и его содержимым);
    в этой книге термин «композиция» используется лишь для обозначения коллекции встраиваемых объектов. Вообще составные классы реализу
    ют все свои интерфейсы, управляя работой встраиваемых объектов.
    Теперь, когда у нас имеются реализации классов работников, объеди
    ним их в коллектив пиццерии и позволим им приступить к работе. На
    ша пиццерия – это составной объект: в нем имеется печь и работники,
    такие как официанты и повара. Когда приходит клиент и делает заказ,
    все компоненты пиццерии начинают действовать – официант прини
    мает заказ, повар делает пиццу и т. д. Следующий пример (файл pizza+
    shop.py
    ) имитирует все объекты и взаимоотношения между ними:
    from employees import PizzaRobot, Server class Customer:
    def __init__(self, name):
    self.name = name def order(self, server):
    print self.name, "orders from", server def pay(self, server):
    print self.name, "pays for item to", server class Oven:
    def bake(self):
    print "oven bakes"

    644
    Глава 25. Шаблоны проектирования с классами class PizzaShop:
    def __init__(self):
    self.server = Server('Pat') # Встроить другие объекты
    self.chef = PizzaRobot('Bob') # Робот по имени bob
    self.oven = Oven()
    def order(self, name):
    customer = Customer(name) # Активизировать другие объекты
    customer.order(self.server) # Клиент делает заказ официанту
    self.chef.work()
    self.oven.bake()
    customer.pay(self.server)
    if __name__ == "__main__":
    scene = PizzaShop() # Создать составной объект
    scene.order('Homer') # Имитировать заказ клиента Homer
    print '...'
    scene.order('Shaggy') # Имитировать заказ клиента Shaggy
    Класс PizzaShop – это контейнер и контроллер – это конструктор, кото
    рый создает и встраивает экземпляры классов работников, написан
    ные нами в предыдущем разделе, а также экземпляры класса Oven, ко
    торый определен здесь. Когда программный код самопроверки этого модуля вызывает метод order класса PizzaShop, встроенным объектам предлагается приступить к выполнению своих обязанностей. Обратите внимание, что для каждого клиента мы создаем новый экземпляр класса Customer и передаем встроенный объект Server (официант) мето
    дам класса Customer (клиент) – клиенты приходят и уходят, а офици
    ант остается частью коллектива пиццерии. Кроме того, обратите вни
    мание, что работники попрежнему вовлечены во взаимосвязи насле
    дования – композиция и наследование – это взаимодополняющие ин
    струменты. Если запустить этот модуль, наша пиццерия обслужит два заказа – один от Гомера (Homer) и другой от Шагги (Shaggy):
    C:\python\examples> python pizzashop.py
    Homer orders from
    Bob makes pizza oven bakes
    Homer pays for item to
    Shaggy orders from
    Bob makes pizza oven bakes
    Shaggy pays for item to
    Это всего лишь игрушечная имитация, но объекты и взаимодействия между ними наглядно демонстрируют составные объекты в действии.
    Классы могут представлять практически любые объекты и взаимоот
    ношения между ними, которые можно выразить словами; для этого просто замените имена существительные классами, глаголы – мето
    дами, и вы получите первый черновой набросок проекта.

    ООП и композиция: взаимосвязи типа «имеет»
    645
    Еще раз об обработке потоков
    Рассмотрим более реалистичный пример использования приема ком
    позиции. Вспомните универсальную функцию обработки потоков дан
    ных, которая частично была реализована во введении в ООП в главе 22:
    def processor(reader, converter, writer):
    while 1:
    data = reader.read()
    if not data: break data = converter(data)
    writer.write(data)
    Однако вместо простой функции мы могли бы реализовать обработку в виде класса, который использует прием композиции, чтобы обеспе
    чить поддержку наследования и более удобную конструкцию про
    граммного кода. Одна из возможных реализаций этого класса содер
    жится в файле streams.py и приводится в ниже:
    class Processor:
    def __init__(self, reader, writer):
    self.reader = reader self.writer = writer def process(self):
    while 1:
    data = self.reader.readline()
    if not data: break data = self.converter(data)
    self.writer.write(data)
    def converter(self, data):
    assert 0, 'converter must be defined'
    При таком подходе объекты чтения и записи встраиваются в экземп
    ляр класса (композиция), а логика преобразования поставляется в ви
    де подкласса, а не в виде отдельной функции (наследование). Ниже приводится содержимое файла converters.py:
    from streams import Processor class Uppercase(Processor):
    def converter(self, data):
    return data.upper()
    if __name__ == '__main__':
    import sys
    Uppercase(open('spam.txt'), sys.stdout).process()
    Здесь класс Uppercase наследует логику цикла обработки потока дан
    ных (и все остальное, что может присутствовать в суперклассах). В нем необходимо определить лишь то, что будет уникальным для него – ло
    гику преобразования данных. Если запустить этот файл, он создаст и запустит экземпляр класса Uppercase, который прочитает содержимое файла spam.txt, преобразует все символы в верхний регистр и выведет их в поток stdout:

    646
    Глава 25. Шаблоны проектирования с классами
    C:\lp3e> type spam.txt
    spam
    Spam
    SPAM!
    C:\lp3e> python converters.py
    SPAM
    SPAM
    SPAM!
    Для обработки потоков различных видов достаточно передать конст
    руктору класса объекты требуемых типов. Ниже приводится пример реализации вывода в файл вместо стандартного потока вывода:
    C:\lp3e> python
    >>> import converters
    >>> prog = converters.Uppercase(open('spam.txt'), open('spamup.txt', 'w'))
    >>> prog.process()
    C:\lp3e> type spamup.txt
    SPAM
    SPAM
    SPAM!
    Но, как предлагалось ранее, мы могли бы также реализовать объекты,
    обернутые в классы, которые определяют необходимые интерфейсные методы ввода и вывода. Ниже приводится простой пример, где вывод осуществляется через класс, который обертывает выводимый текст в теги HTML:
    C:\lp3e> python
    >>> from converters import Uppercase
    >>>
    >>> class HTMLize:
    ... def write(self, line):
    ... print '
    %s
    ' % line[: 1]

    >>> Uppercase(open('spam.txt'), HTMLize()).process()
    SPAM
    SPAM
    SPAM!
    Если проследить порядок выполнения этого примера, можно заметить,
    что было получено два варианта преобразований – приведение симво
    лов к верхнему регистру (наследованием) и преобразование в формат
    HTML (композицией), хотя основная логика обработки в оригиналь
    ном суперклассе Processor ничего не знает ни об одном из них. Про
    граммному коду, выполняющему обработку, нужны только метод write
    – в классах, выполняющих запись, и метод convert. Его совер
    шенно не интересует, что делают эти методы. Такой полиморфизм и инкапсуляция логики составляют основу такой мощи классов.

    ООП и композиция: взаимосвязи типа «имеет»
    647
    В этом примере суперкласс Processor предоставляет только цикл ска
    нирования файла. Для выполнения более существенных действий его можно было бы расширить, чтобы обеспечить поддержку дополни
    тельных инструментов в его подклассах и постепенно превратить все это в полноценную платформу. Создав такой инструмент один раз, вы
    Придется держать в уме: классы и их хранение
    В этой части книги я уже несколько раз упоминал о возможности сохранения объектов, потому что этот метод особенно хорошо ра
    ботает с экземплярами классов. Например, помимо возможности имитировать взаимодействия в реальном мире, классы, разрабо
    танные для пиццерии, могли бы также использоваться как осно
    ва базы данных пиццерии. Экземпляры классов могут сохранять
    ся на диск за одно действие – с помощью модулей pickle или shelve
    . Интерфейс модуля pickle очень прост в использовании:
    import pickle object = someClass()
    file = open(filename, 'wb') # Создать внешний файл
    pickle.dump(object, file) # Сохранить объект в файле
    import pickle file = open(filename, 'rb')
    object = pickle.load(file) # Позднее извлечь обратно
    Модуль pickle преобразует объекты, находящиеся в памяти, в по
    следовательные потоки байтов, которые можно сохранять в фай
    лах, передавать по сети и т. д. При извлечении объектов проис
    ходит обратное преобразование: из последовательного потока байтов в идентичные объекты в памяти. Модуль shelve реализует похожую возможность, но он автоматически сохраняет объекты в базе данных с доступом по ключу, которая предоставляет ин
    терфейс, похожий на интерфейс словаря:
    import shelve object = someClass()
    dbase = shelve.open('filename')
    dbase['key'] = object # Сохранить под ключом key
    import shelve dbase = shelve.open('filename')
    object = dbase['key'] # Позднее извлечь обратно
    В нашем примере использование классов для моделирования ра
    ботников означает, что можно достаточно легко создать простую базу данных сотрудников и пиццерий: записывая экземпляры объектов в файл, мы сможем сохранять их между запусками программы. Более подробную информацию о сохранении ищите в руководстве к стандартной библиотеке.

    1   ...   74   75   76   77   78   79   80   81   ...   98


    написать администратору сайта