Саммерфилд - Программирование на Python 3 (1077331), страница 61
Текст из файла (страница 61)
Как будет показано ниже, использование свойств упрощает проверку корректности данных. Внутри метода (который является обычной функцией, получающей в виде первого аргумента конкретный экземпляр класса, в контексте которого выполняются действия) потенциально доступны несколько разновидностей переменных. К переменным экземпляра можно обращаться посредством квалификации их имен самим экземпляром. Внутри методов могут создаваться локальные переменные — доступ 277 Объектно-ориентированный подход к ним осуществляется без квалификации имени.
Доступ к переменным класса (иногда они называются статическими переменными) может осуществляться посредством квалификации их имен именем класса. Доступ к глобальным переменным, то есть к переменным модуля, осуществляется без квалификации их имен. В некоторых книгах, посвященных языку РусЬоп, используется понятие пространства имен — отображения имен на объекты. Модули— это пространства имен.
Например, выполнив инструкцию 1ярог1 ааГб, мы получаем возможность обращаться к объектам в модуле эа1л, квалифицируя их именем пространства имен (например, васс. Ш или вэгл. э1л( )). Точно так же классы и объекты являются пространствами имен. Например, если представить, что была выполнена инструкция э = сояр1ех(1, 2), то пространство имен объекта г будет содержать два доступных нам атрибута (х. геа1 и з. 1вао).
Одно из преимуществ объектно-ориентированного подхода состоит в том, что если у нас имеется класс, мы можем специализировать его. Это означает, что можно создать новый класс, наследующий все атрибуты (данные и методы) из оригинального класса, и добавить в него или заместить некоторые методы, или добавить дополнительные переменные экземпляра.
Мы можем создать подкласс (другое название специализации) любого класса РуьЬоп, будь то встроенный класс, класс из стандартной библиотеки' или один из наших собственных классов. Возможность специализации — одно из важнейших преимуществ объектно-ориентированного программирования, поскольку она упрощает использование существующих классов, с опробованными и проверенными функциональными возможностями, в качестве основы для новых классов, расширяющих оригинал, добавляя новые атрибуты данных или новые функциональные возможности простым и понятным способом. Более того, имеется возможность передавать объекты новых классов функциям и методам, которые были написаны для работы с оригинальным классом, и при этом они будут работать вполне корректно.
Мы будем использовать термин базовый класс для обозначения наследуемого класса. Базовым классом может быть как прямой предок, так и любой другой класс, расположенный выше в дереве наследования. Другой термин, обозначающий базовый класс, — суперкласс. Мы будем использовать термины подкласс, порожденный класс и дочерний класс для обозначения класса, наследующего (то есть специализирующего) другой класс. В языке РуьЬоп все встроенные и библиотечные классы, а также все созданные нами классы прямо или косвенно на- Некоторые библиотечные классы, реализованные яа языке С, не могут быть специализированы.
Такая особенность этих классов обязательно подчеркивается в документации. Глава б. Обьектно-ориентированное программирование 278 Суперкласс для классов Отсс, Иуваст,... Базовый класс для классов озст, иувтст,... Подкласс класса оь)ест Специализация класса оЬЗест Дочерний класс класса оЬЗест Суперкласс для класса иувзст,... Базовый класс для класса Иуьзст,... Подкласс класса оь)ест Специализация класса оьзест Дочерний класс класса ооЗест Подкласс класса гдст Специализация класса озст Дочерний класс класса озст Рис.6.1. Некоторые термины, используемые при описании механизма наследоеания В терминологии языка С++ все методы классов в языке РуьЬоп являются виртуальными.
следуют единый базовый класс оь)ест. Рис. 6.1 иллюстрирует некоторые термины, используемые при описании механизма наследования. Любой метод можно переопределить, то есть повторно реализовать в подклассе, как в языке дауа (за исключением методов со специфика- тором 11пз1).' Если предположить, что имеется объект класса Иу01ст (наследующего класс 01ст) и производится вызов метода, который определяется обоими классами 01ст и йу0101, интерпретатор корректно вызовет версию метода для класса йу0101.
Этот механизм называется динамическим связыванием методов или нолиморфизмом. Если возникнет необходимость вызвать версию метода базового класса внутри одноименного метода подкласса, сделать это можно с помощью встроенной функции вцрвг(). Кроме того, в языке Ру1Ьоп используется механизм грубого олределения типа (так называемая утиная типизация) — «если это ходит как утка и крякает как утка, значит, это утка».
Говоря другими словами, если нам необходимо вызвать определенный метод объекта, то неважно, к какому классу относится этот объект, главное, чтобы он имел метод, который предполагается вызвать. В предыдущей главе мы видели, что, когда возникала необходимость в объекте файла, мы могли получить его, вызвав функцию среп() или создав объект 10.8тгтп010, который имеет тот же самый АР1 (Арр11саь(оп Ргоягаппшпя 1пьег1асе— прикладной программный интерфейс), то есть обладает теми же самыми методами, что и объект, возвращаемый функцией орвп(), открывающей файл в текстовом режиме.
Механизм наследования используется для моделирования отношений типа «является», то есть отношения, когда объекты одного класса по Собственные классы 279 существу являются теми же самыми, что и объекты какого-то другого класса, но с некоторыми отличиями, такими как дополнительные атрибуты данных или дополнительные методы.
Другой подход основан на использовании механизма агрегирования (или композиции) — когда класс включает одну или более переменных экземпляра, являющихся экземплярами других классов. Механизм агрегирования используется для моделирования отношений типа «имеет». В языке РуФЬоп при создании любых классов используется механизм наследования, потому что все классы в конечном итоге имеют единый базовый класс оо) ест, и, кроме того, в большинстве классов используется механизм агрегирования, потому что в большинстве классов имеются переменные экземпляров различных типов.
Некоторые объектно-ориентированные языки программирования обладают двумя особенностями, отсутствующими в языке Ру$Ьоп. Первая особенность — это перегрузка, то есть возможность иметь в одном и том же классе несколько методов с одинаковыми именами, но с различными списками входных параметров. Благодаря наличию в языке Ру$Ьоп очень гибкого механизма передачи аргументов отсутствие возможности перегрузки практически не является ограничением.
Вторая особенность — управление доступом; в языке РуФЬоп не существует абсолютно надежных механизмов защиты частных данных. Однако если мы создаем атрибуты (переменные экземпляра или методы), имена которых начинаются двумя символами подчеркивания, интерпретатор будет предотвращать неумышленные попытки доступа к ним, так что эти атрибуты можно считать частными. (Делается это посредством подмены имен, как будет показано на примере в главе 8.) Аналогично тому, как мы первый символ имени наших собственных модулей писали в верхнем регистре, мы будем поступать и при именовании наших собственных классов. Мы можем определить любое число классов, как в самой программе, так и в модулях.
Имена классов не обязательно должны соответствовать именам модулей, и модули могут содержать столько определений классов„сколько нам потребуется. Теперь, когда мы рассмотрели некоторые проблемы, которые могут быть решены с помощью классов, познакомились с необходимыми терминами и некоторыми основами, — можно приступать к созданию собственных классов.
Собственные классы В предыдущих главах нам уже приходилось создавать собственные классы: наши собственные исключения. Ниже приводится синтаксис, используемый при создании собственных классов: става с2ааааааа витта Глава 6. Объектно-ориентированное программирование гво с1авв с)авв))аде(саве с1аввев): вите Поскольку при создании подклассов исключений мы не добавляли никаких новых атрибутов (данных экземпляра или методов), в качестве блока кода (впав) мы использовали инструкцию рава (то есть ничего не добавляли), а так как блок кода состоял из единственной инструкции, мы помещали его в одной строке с инструкцией с1авв. Обратите внимание: как и инструкция Ое(, инструкция с1авв является самой обычной инструкцией, что дает возможность создавать классы динамически, когда в этом возникнет необходимость.
Методы класса создаются с помощью инструкций Ое( внутри блока кода класса. Экземпляры класса создаются посредством обращения к имени класса, как к функции, которой передаются все необходимые аргументы. Например, инструкция х = совр1ех(4, 8) создаст комплексное число и запишет ссылку на него в переменную х. Атрибуты и методы Начнем с очень простого класса Р01п1, который хранит координаты точки (х, у). Определение класса находится в файле БИаре.ру, а ниже приводится его полная реализация (за исключением строк документи- рования): с1двв Ротс(: Оет 1птт (ве1(, х=О, у=О); ВЕ1(.х = х ве1бу = у оег отвтапсе ггое огпцтп(ве1().
гетсгп ватп.пурот(ве1(.х, ве1г.у) Оет ец (ве1(, отЬег): гетега ве1б х == отвег.х апв ве1(.у == отЬег.у Оет герг (ве1(); гетега ГРотпт((О.х.'г), (О.у!г))".(огеат(ве1() оег втг (ве1(): гетсгп "((О х!г), (О.у!г))".Гогват(ве1Г) тарогт Бпаре а = ЯПаре,РЬ(пт() герг(а) Ь = 8Паре.Ротпт(3, 4) Втг(Ь) Ь.О1втапсе Ггое ог1цтп() Ь.х = -19 Ф вернет: 'Ро!пт(0, 0)' Ф вернет: '(3, 4)' а вернет; 5.0 Поскольку базовый класс не был указан явно, класс Ротс( является прямым наследником класса оЬ)ест, как если бы было записано определение с1авв Ро1пт(оЬ)ест).