Лутц М. - Изучаем Python (1077325), страница 136
Текст из файла (страница 136)
Перегрузка операторов и наследование — автоматический поиск атрибутов в подразумеваемом дереве классов — это главные механизмы 635 В заключение ООП. В конечном итоге именно они позволяют классу Еар1оуее, находящемуся в самом низу дерева, получать большую часть своего поведения »просто так», что составляет главную идею ООП. В заключение В этой главе был предпринят второй, более глубокий тур по механизмам ООП в языке РуФЬоп. Мы узнали еще больше о классах и методах, о наследовании и методах перегрузки операторов. Мы также закончили повествование о пространствах имен в языке Ру$Ьоп, расширив это понятие, чтобы охватить его применение к классам.
По пути мы рассмотрели еще несколько дополнительных концепций, таких как абстрактные суперклассы, атрибуты данных класса и вызов методов и конструкторов суперкласса вручную. В заключение мы изучили большой пример, соединяющий в себе многое из того, что мы уже знаем об ООП. Теперь, когда мы знаем все о программировании классов в языке Ру$1зоп, в следующей главе мы обратимся к некоторым распространенным шаблонам проектирования — способам использования и комбинирования классов для оптимизации многократного использования программного кода. Часть сведений в этой главе напрямую не связана с языком Ру$Ьоп, но ее необходимо знать для грамотной работы с классами. Однако, прежде чем приступить к чтению, ответьте на обычные контрольные вопросы, чтобы освежить в памяти все, о чем говорилось в этой главе.
Закрепление пройденного Контрольные вопросы 1. Что такое абстрактный суперкласс7 2. Какие два метода перегрузки операторов можно использовать для поддержки итераций в классах7 3. Что произойдет, когда простая инструкция присваивания появится на верхнем уровне в инструкции с1азз? 4. Зачем может потребоваться в классе вручную вызывать метод 1л?С суперкласса? б.
Как можно расширить унаследованный метод вместо полного его замещения7 6. Какие методы вызываются в последнем примере этой главы, когда выводится экземпляр зсе класса Еар1оуее7 7. Назовите... столицу Ассирии. Ответы 1. Абстрактный суперкласс — это класс, который вызывает методы, но не наследует и не определяет их. Он ожидает, что методы будут реа- 636 Глава 24, Подробнее о программировании классов лизованы в подклассах.
Часто такой прием используется для обобщения классов, когда поведение будущих подклассов трудно предсказать заранее. Платформы ООП также используют этот прием для выполнения операций, определяемых клиентом. 2. Классы могут обеспечить поддержку итераций, определив (или унас- ледовав) метод де!!геа или !гег, Во всех итерационных контекстах интерпретатор Ру!)юп сначала пытается использовать метод Пег (который возвращает объект, поддерживающий итерационный протокол в виде метода пек!): если метод Пег не будет найден в результате поиска по дереву наследования, интерпретатор возвращается к использованию метода извлечения элемента по его индексу десыеа (который вызывается многократно и при каждом вызове получает постоянно увеличивающиеся значения индексов). 3.
Когда простой оператор присваивания (Х = У) появляется на верх- нем уровне в инструкции о1авв, он присоединяет к классу атрибут данных (С1авв, Х). Как и все атрибуты класса, этот атрибут будет совместно использоваться всеми экземплярами. При этом атрибуты данных не являются вызываемыми методами. 4. Вручную вызывать метод !и!! суперкласса может потребовать- ся, когда класс определяет свой собственный конструктор 1пы и при этом необходимо, чтобы выполнялись действия, предусмотренные конструктором суперкласса.
Интерпретатор Ру!)юп автоматически вызывает только один конструктор — самый нижний в дереве наследования. Конструктор суперкласса вызывается через имя класса, и ему вручную передается аргумент ве1Г: Зорегс1авв. 1п!! (ве1Г, ,). 5. Чтобы расширить унаследованный метод вместо полного его заме- щения, нужно переопределить его в подклассе и при этом вручную вызвать версию метода суперкласса из нового метода в подклассе.
То есть вручную передать версии метода суперкласса аргумент яе1(: Зорегс1авв.ае!Поз(ве1Г, ...). 6. В конечном итоге при выводе экземпляра все будет вызван метод Зе- пег!сд!вр1ау. в!г, который в свою очередь вызовет метод Зепег! од!вр1ау. да!Пеглш гв. Подробнее, при печати экземпляра все инструкция рып! преобразует информацию в объекте в удобочитаемую строку, передавая объект встроенной функции вгг. Для класса это означает попытку отыскать в дереве наследования метод перегрузки оператора в! г и, в случае успеха, его вызов. Объект все является экземпляром класса Епр1оуее, который сам не имеет метода вгг поэтому следующий цикл поиска производится в классе Регвоп и метод в!г будет найден в конце концов в классе Зепеыод!вр1ау.
7. Ашшур (или Калат-Шеркат), Калах (или Нимруд), короткое время был столицей Дур-Шаррукин (или Хорсабад) и, наконец, Ниневия. Шаблоны проектирования с классами До сих пор в этой части книги мы все свое внимание уделяли использованию объектно-ориентированных инструментов языка Ру$Ьоп классов. Но ООП вЂ” это еще и задача проектирования: как использовать классы для моделирования полезных объектов. В этой главе мы коснемся некоторых базовых идей ООП и рассмотрим несколько дополнительных примеров, более реалистичных, чем мы видели до сих пор. Многие термины, упоминающиеся здесь (делегирование, композиция, фабрики и другие), требуют более подробного пояснения, чем я приводил ранее в этой книге. Если эта тема вызывает у вас интерес, я предлагаю в качестве следующего шага взяться за изучение книг, посвященных шаблонам проектирования в ООП. Ру~йоп и ООП Реализацию ООП в языке Ру(Ьоп можно свести к трем следующим идеям: Наследование Наследование основано на механизме поиска атрибутов в языке Ру- (поп (в выражении Х.
паве). Полиморфизм Назначение метода ае1Ьса в выражении Х.аетсса зависит от типа (класса) Х. Инкапсуляция Методы и операторы реализуют поведение; сокрытие данных — это соглашение по умолчанию. К настоящему времени вы уже доч:кны иметь представление о том, что такое наследование в языке РуьЬоп. Кроме того, мы уже несколько раз говорили о полиморфизме в языке РуФЬоп — он произрастает из отсутст- 638 Глава 25. Шаблоны проектирования с классами вия объявления типов в языке РуСЬоп. Нескольку разрешение имен атрибутов производится на этапе выполнения, объекты, реализующие одинаковые интерфейсы, являются взаимозаменяемыми — клиентам не требуется знать тип объекта, реализующего вызываемый метод.
Инкапсуляция в языке РуСЬоп означает упаковывание — то есть сокрытие подробностей реализации за интерфейсом объекта. Это не означает принудительное сокрытие, как будет показано в главе 26. Инкапсуляция позволяет изменять реализацию интерфейсов объекта, не оказывая влияния на пользователей этого объекта. Использование перегрузки путем сигнатур вызова (или отказ от нее) В некоторых объектно-ориентированных языках под полиморфизмом также понимается возможность перегрузки функций, основанной на сигнатурах типов их аргументов. Но, так как в языке Рувйол отсутствуют объявления типов, эта концепция в действительности здесь не применима — полиморфизм в языке РуСЬоп основан на интерфейсах объектов, а не на типах.
Вы можете попробовать выполнить перегрузку методов, изменяя списки их аргументов, как показано ниже: с!авв С: Се! яавйве)1, к): СЕ! аЕСП!ВЕ11, к, у, г): Это вполне работоспособный программный код, но, так как инструкция сеу просто присваивает объект имени в области видимости класса, сохранено будет только последнее определение метода (это все равно, что записать две инструкции подряд: Х = 1, а затем Х = 2, в результате чего Х будет иметь значение 2).
Выбор на основе типа всегда можно реализовать с помощью идеи проверки типа, с которой мы встречались в главах 4 и 9, или с помощью возможности передачи списка аргументов, обсуждавшейся в главе 16: с1авв С: Сау аеСП(ве11, *асса): !Г 1еп(асдв) == 1: е1!Г суре(асдсд)) == впс: Однако обычно этого следует избегать, потому что, как описывалось в главе 16, следует писать такой код, который полагается на интерФейс объекта, а не на конкретный тип данных. Такой подход полезнее, так как охватывает более широкие категории типов и приложений, как нынешних, так и тех, что появятся в будущем: 639 Классы как записи с1авв С: Оет ввсл(ве1(, х): х.орегв(топ() № Предполагается, что к работает правильно Кроме того, считается, что лучше выбирать разные имена для методов, выполняющих разные операции, и не полагаться на сигнатуры вызова (при этом неважно, какой язык программирования вы используете).
Классы как записи В главе 8 демонстрировалось, как использовать словари для хранения записей свойств сущностей в программах. Рассмотрим этот прием более подробно. Ниже приводится пример использования записи на основе словаря, использовавшийся ранее: »> гес = () »> гас['паве'3 'ве1' »> гес['асе 3 40 »> тес[')оЬ'3 'вга1пег/вг11вг' »> »> рг1п1 гас['паве'] ве1 Этот фрагмент имитирует инструмент, напоминающий записи и структуры в других языках программирования.
Однако, как мы видели в главе 23, существует еще множество способов сделать то же самое с помощью классов. Ниже приводится, пожалуй, самый простой из них: »> с1авв гес: рава »> гес.паве = 'ве1' »> гвс.асе = 40 »> гес.)оЬ = '1га1пег/вг11ег' »> »> рг1п1 тес.асе 40 Этот вариант существенно компактнее, чем эквивалент на базе словаря. Здесь для создания объекта пустого пространства имен используется пустая инструкция с1авв (обратите внимание на инструкцию равв— этого требует синтаксис, так как в данном случае класс не содержит никакой логики, которую необходимо было бы реализовать), Создав пустой класс, мы заполняем его, присваивая значения его атрибутам.
Этот прием работает, но для каждой отдельной записи придется писать новую инструкцию с1авв. Пожалуй, более удобным будет создавать экземпляры класса всякий раз, когда нам потребуется новая запись: »> с1авв гес; рава »> регв1 = гвс() »> регв1.пава = 'ве1' 640 Глава 25. Шаблоны проектирования с классами »> регат.)оЬ = 'Сга1пвг' »> регат.аре = 40 »> »> регв2 = гео() »> регв2.паве = 'баче' »> регв2.)оо = 'вече)орег' »> »> регат.паве, регв2.паап ('ве1', 'паче') Здесь из одного и того же класса были созданы две записи — экземпляры класса начинают свое существование пустыми, как и классы. После втого производится заполнение записей путем присваивания значений атрибутам. Однако на этот раз существует два отдельных объекта и, соответственно, два разных атрибута паве. Фактически у экземпляров одного и того же класса не обязательно должны быть одинаковые наборы имен атрибутов.
В данном примере один из экземпляров имеет уникальный атрибут аре. Экземпляры класса действительно являются разными пространствами имен; каждый из них имеет свой словарь атрибутов. Обычно экземпляры единообразно наполняются атрибутами в методах класса, тем не менее, они обладают большей гибкостью, чем можно было бы ожидать. Наконец, для реализации записи мы могли бы написать более полноценный класс: »> о1авв Регвоп: бет !п11 (ве1Г, паве, )оЬ): вв1(.паве = паев ве1Г.)оЬ = )оо бег 1пто(ве1Г); ге!ого (ве1(.паве, ве1Г.)оо) »> вага = Регвоп('а1', '!та!пег') »> вача = Регвоп('ба', 'бече1орег') »> »> аагК.)оь, саче.!пто() ('тгатпег', ('ба', 'бече!орет')) Такая схема также допускает создание множества экземпляров, но на этот раз класс уже не пустой: мы добавили в него логику (методы) инициализации экземпляров на этапе создания и сбора атрибутов в кортеж.