Лутц М. - Изучаем Python (1077325), страница 138
Текст из файла (страница 138)
Одна из возможных реализаций этого класса содержится в файле ззгеатз.ру и приводится в ниже: с1авв Ргооеввог: бег Сп1с (ве1(, гезбег, чг1сег): ве1(.геабег = гевбег ве1(.гггстег = гчгССег бет ргооевв(ве!т). »Ы1е 1: баса = ве1(. геабег. геаб11пе() 1( пос баса: ьгезк баса = ве1г.сопчегсег(баса) ве1( г«ггтег.чгССЕ(бата) бет сопчегтег(ве1(, бата): аввегс О, 'сопчегсег вовс ье бе(спер' При таком подходе объекты чтения и записи встраиваются в экземпляр класса (композиция), а логика преобразования поставляется в виде подкласса, а не в виде отдельной функции (наследование).
Ниже приводится содержимое файла соя иегтегв.ру: тгое всгеаев сэрогс Ргооеввог с1звв Ьрреговве(ягооеввог): бет сопчегтег(ве1(, бата): гетогп баСв.оррег() !( паве == ' яавп гарогт вув бррегоаве(ореп('враз.ткт'), вув.в(бом ).Ргосезв() Здесь класс ()ррегсаве наследует логику цикла обработки потока данных (и все остальное, что может присутствовать в суперклассах). В нем необходимо определить лишь то, что будет уникальным для него — логику преобразования данных.
Если запустить этот файл, он создаст и запустит экземпляр класса ()рре гсазе, который прочитает содержимое файла зраатххт, преобразует все символы в верхний регистр и выведет их в поток вСбоот: б46 Глава 25, Шаблоны проектирования с классами С:'т1рзе> Туре враз.тх1 врае Брав ЯРАМ! С:'11РЗв> рутвоп сопчегтегв.ру БРАМ БРАМ ЯРАМ! Для обработки потоков различных видов достаточно передать конструктору класса объекты требуемых типов. Ниже приводится пример реализации вывода в файл вместо стандартного потока вывода: С:~1рзе> рутлоп »> 1врогт сопчег1вгв »> ргоо = сопчегтегв,Уррегсавв(орел('арве.
тх1'), орел('враеор. 1х1', 'е')) »> ргов.ргосевв() С:11РЗе> суре вравср.Сх1 ЯРАМ ЯРАМ БРАМ' Но, как предлагалось ранее, мы могли бы также реализовать объекты, обернутые в классы, которые определяют необходимые интерфейсные методы ввода и вывода. Ниже приводится простой пример, где вывод осуществляется через класс, который обертывает выводимый текст в теги НТМ1л С, т1РЗе> рутлоп »> тгое сопчегтегв 1врог1 Уррегсаве »> »> с1авв НТМЕ1ве: Оет иг11е(ве)Г, 11пе): рг1пт '<РВЕ>Хв</РВЕ>' 1 11пе[:-1) »> Уррегсаве(орел('прав,тхт'), НТМ[1ае()).ргосевв() <РЯЕ>БРАМ</РЯЕ> <РЯЕ>ЯРАМ</РЯЕ> <РЯЕ>БРАМ'</РЯЕ> Если проследить порядок выполнения этого примера, можно заметить, что было получено два варианта преобразований — приведение символов к верхнему регистру (наследованием) и преобразование в формат НТМ1 (композицией), хотя основная логика обработки в оригинальном суперклассе Ргссеяяог ничего не знает ни об одном из них.
Программному коду, выполняющему обработку, нужны только метод иг11е — в классах, выполняющих запись, и метод сслчегт. Его совершенно не интересует, что делают зти методы. Такой полиморфизм и инкапсуляция логики составляют основу такой мощи классов. 647 ООП и композиция: взаимосвязи типа «имеетл Придется держать в уме: классы и их хранение В этой части книги я уже несколько раз упоминал о возможности сохранения объектов, потому что этот метод особенно хорошо работает с экземплярами классов.
Например, помимо возможности имитировать взаимодействия в реальном мире, классы, разработанные для пиццерии, могли бы также использоваться как основа базы данных пиццерии. Экземпляры классов могут сохраняться на диск за одно действие — с помощью модулей р1сЬ1е или впе)че. Интерфейс модуля ртсК1е очень прост в использовании: тарогт ртсК1е оо)ест = воаеС1авв() Гт1е = орел(тт1епаае, 'ио') ртсК1е.боер(ЬЬ)ест, Гз1е) № Создать внвюний файл № Сохранить объект в файле тарогт р1ск1е (11е = орел(тт1епаае, 'гЬ') оь)ест = р1ск1е.1оао(т!1е) № Позднее извлечь обратно Модуль р1ск1е преобразует объекты, находящиеся в памяти, в последовательные потоки байтов, которые можно сохранять в файлах, передавать по сети и т. д.
При извлечении объектов происходит обратное преобразование: из последовательного потока байтов в идентичные объекты в памяти. Модуль впе1че реализует похожую возможность, но он автоматически сохраняет объекты в базе данных с доступом по ключу, которая предоставляет интерфейс, похожий на интерфейс словаря: 1арогт впе1че оо)ест = воаеС1авв() ОЬаве = вье1че преп('т~)епаае') Ооаве('Кеу'] = оЬ)ест № Сохранить под ключои кву варогт вое1че Ооаве = вае1че.орел('тт1епаае') оь)ест = оьаве('кеу'] № Позднее извлечь обратно В нашем примере использование классов для моделирования работников означает, что можно достаточно легко создать простую базу данных сотрудников и пиццерий: записывая экземпляры объектов в файл, мы сможем сохранять их между запусками программы.
Более подробную информацию о сохранении ищите в руководстве к стандартной библиотеке. В этом примере суперкласс Ргосеввог предоставляет только цикл сканирования файла. Для выполнения более существенных действий его можно было бы расширить, чтобы обеспечить поддержку дополнительных инструментов в его подклассах и постепенно превратить все это в полноценную платформу. Создав такой инструмент один раз, вы 648 Глава 25. Шаблоны проектирования с классами сможете многократно использовать его во всех своих программах. Даже в этом простом примере благодаря тому, что с помощью классов можно упаковать и унаследовать так много, все, что нам пришлось сделать — это реализовать этап преобразования в формат НТМЬ, а все остальное у нас уже и так имелось.
Еще один пример композиции в действии приводится в упражнении 9 в конце главы 26, а его решение — в приложении В. Он напоминает пример с пиццерией. В этой книге мы сосредоточились на наследовании, потому что это основной инструмент, который обеспечивает объектно-ориентированные возможности в языке Ру()топ. Однако на практике прием композиции используется ничуть не реже, чем наследование, в качестве способа организации классов, особенно в крупных системах, Как мы видели, наследование и композиция — часто взаимодополняющие (а иногда и альтернативные) приемы. Композиция — это проблема проектирования, которая далеко выходит за рамки языка Ру()топ и этой книги, поэтому полный охват этой темы я оставляю за другими источниками информации.
ООП и делегирование В ООП часто используется термин делегирование, под которым обычно подразумевается наличие объекта-контроллера, куда встраиваются другие объекты, получающие запросы на выполнение операций. Контроллеры могут решать административные задачи, такие как слежение за попытками доступа и т. д. В языке Ру1)топ делегирование часто реализуется с помощью метода детаттг, потому что он перехватывает попытки доступа к несуществующим атрибутам. Класс-обертка (иногда называется прокоп-классом) может использовать метод десзттг, для перенаправления обращений к обернутому объекту. Класс-обертка имеет интерфейс обернутого объекта и может добавлять дополнительные операции.
В качестве примера рассмотрим файл гласе.ру: с1аьв агвррег: оег 1пы (ве1(, оозест): ве1т.агеррес = ос)ест в Сохранить орьвкт оет детвыг (ве1т, вттгпвае): ргтпт 'тгвсе ', аттгпвае В Отивтить фвкт извлечения гетогп детвмг(ве1(.агвррес, выгпаае) в двлвгироввть извлвяение В главе 24 говорилось, что метод детаттг получает имя атрибута в виде строки.
В этом примере для извлечения из обернутого объекта атрибута, имя которого представлено в виде строки, используется встроенная функция детаттг; вызов детзттг(Х, И) аналогичен выражению Х И за исключением того, что И вЂ” это выражение, которое во время выполнения представлено строкой, а не именем переменной. Фактически вызов детаттг(Х, И) по его действию можно сравнить с выраженн- Множественное наследование 64«9 ем Х Стст [й], только в первом случае дополнительно выполняется поиск в дереве наследования, как в выражении Х. (1, а во втором — нет (подробнее об атрибуте с[от рассказывается в разделе «Словари пространств имен«в главе 24).
Такой прием, реализованный в этом классе-обертке, можно использовать для управления доступом к любому объекту с атрибутами — спискам, словарям и даже к классам и экземплярам. Ниже приводится класс нгаррег, который просто выводит сообщение при каждом обращении к атрибуту и делегирует этот запрос обернутому объекту нгарреш »> Ггоа 1гасе (арогт игаррвг »> х = игаррег([1,2,3]) »> х.аррепб(4) тгасв' аррепс »> хлкгарреа [1, 2. 3, 4] № Обернуть список № Делегировать операцию методу списка № Вмвести оберггутий объект »> х = игаррег(("а": 1, "Ь"; 2)) № Обернуть словарь »> х.кеув() № делегировать операцию метолу сповврп тгасе; кеув ['а', 'Ь'] В результате интерфейс обернутого объекта расширяется за счет методов класса-обертки.
Этот подход может использоваться для регистрации вызовов методов, перенаправления вызовов методов дополнительному или адаптированному программному коду и т. д. В главе 26 мы еще вернемся к обернутым объектам и делегированию операций как к одному из способов расширения встроенных типов. Если шаблон проектирования с делегированием заинтересовал вас, тогда смотрите обсуждение декораторов функций в главе 26 — это очень близкая концепция, призванная обеспечить расширение отдельных функций и методов, а не всего интерфейса объекта.