Динамический полиморфизм Денис С. Мигинский. Основные принципы ООП Абстракция + Инкапсуляция + Модульность = SoC + KISS Наследование + ( динамический.

Презентация:



Advertisements
Похожие презентации
OOП Инна Исаева. Подпрограмма – это большая программа, разделённая на меньшие части. В программе одна из подпрограмм является главной. Её задача состоит.
Advertisements

Объектная модель Ruby Денис С. Мигинский. Основные характеристики объектной модели Обязательные: Поддержка классов Поддержка описания поведения класса.
Методология объектно- ориентированного программирования.
1 © Luxoft Training 2012 Введение в ООП Модуль #2.
Объекто-ориентированное проектирование Паттерны проектирования. 28 февраля 2008 г. 4 курс Технологии программирования.
Наследование и полиморфизм. «Быть» или «Иметь» а так же «Точно» или «Как получится»
Объектно-ориентированное программирование С++. Лекция 6 Карпов В.Э.
Алгоритмический подход – главное алгоритм решения задачи ( в основном, используется для вычислительных задач ); Структурное программирование – декомпозиция,
Парадигмы программирования Денис С. Мигинский. Понятие парадигмы Парадигма (философия науки) – устоявшаяся система научных взглядов, в рамках которой.
Введение в объектно- ориентированное программирование.
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
4. Моделирование функциональных требований к системе.
Принципы объектно-ориентированного программирования Объектная модель Наследование Инкапсуляция Полиморфизм.
Министерство образования Республики Беларусь Белорусский государственный университет Управляющие структуры языков программирования.
Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Наследование Полиморфизм ВЫЗОВ КОНСТРУКТОРОВ И ДЕСТРУКТОРОВ ПРИ НАСЛЕДОВАНИИ.
Java, каф. ОСУ АВТФ1 Методология ООП В формулировке цели выполнения некоторого проекта (например, разработка ИС) участвуют предметы (объекты)
Mock-объекты mock (англ.) – ложный, фиктивный, мнимый, фальшивый, поддельный.
ФУНКЦИИ БОЛЕЕ ВЫСОКОГО ПОРЯДКА Функциональное программирование Григорьева И.В.
Транксрипт:

Динамический полиморфизм Денис С. Мигинский

Основные принципы ООП Абстракция + Инкапсуляция + Модульность = SoC + KISS Наследование + ( динамический ) Полиморфизм = специфичные для ООП механизмы SoC

Общее определение полиморфизма Полиморфизм - способность объектов различных типов обладать одним и тем же публичным контрактом

Разновидности полиморфизма Специальный (ad-hoc) – перегрузка функций. Имеет смысл только в системах со строгой типизацией, иначе вырождается в динамический полиморфизм Параметрический (parametric) – параметризация типов другими типами. Имеет смысл только в системах со строгой типизацией. Примечание : в ФП и теории типов называется просто полиморфизмом Динамический (subtype) – единый контракт для типов, связанных отношением генерализации. Примечание : в ООП называется просто полиморфизмом

Определение наследования Следующие утверждения эквивалентны : 1. Если некоторое утверждение истинно для всех объектов типа T, то это же утверждение истинно для всех объектов типа S. 2. S является подтипом T. ( принцип подстановки Барбары Лисков, LSP) 1=>2: естественное отношения наследования. Пример : утиная типизация. Если нечто выглядит как утка, крякает как утка и плавает как утка, значит это утка.

LSP: пояснение 2=>1: декларируя отношение наследования S

Разделение ответственностей через полиморфизм Вызов обычной функции : зависимость только от контракта функции, но не от ее реализации ; реализация в системе ровно одна. Вызов полиморфной функции ( обобщенная функция, посылка сообщения ): зависимость только от контракта функции, но не от ее реализации ; для параметрического полиморфизма : одна реализация, параметризованная типа (- ами ) для специального и динамического полиморфизма : реализаций в системе несколько, отличаются типами параметров. только для динамического полиморфизма : реализация функции может комбинироваться из частей, которые мало знают ( или вообще ничего не знают ) друг про друга. Простейшая форма комбинации – через super.

« Многомерный » полиморфизм Базовый полиморфизм : одиночное наследование, диспетчеризация по одному параметру ( посылка сообщений ) Расширения : множественное наследование (multiple inheritance) диспетчеризация по нескольким параметрам (multiple dispatch) вспомогательные методы (auxiliary methods)

CLOS: Common Lisp Object System Особенности : Поддерживает все перечисленные формы динамического полиморфизма Поведение определяется в форме обобщенных функций Нет сокрытия состояния ( из - за предыдущего пункта – больше ответственности на программисте ) Можно изменить иерархию классов в динамике, изменить класс объекта « на лету » и т. д. В большинстве реализаций в основе имеет Meta-Object Protocol, что позволяет менять и саму объектную модель ( изменить понятие класса, метода, наследования и т. д.)

Множественное наследование Некоторые формы множественного наследования : Множественная имплементация интерфейсов Аннотирование с помощью интерфейсов ( строго говоря, к ООП отношения не имеет ) Включение примесей Наследование от нескольких классов, не связанных логически ( не имеют общих предков, сообщений / функций ) – представление композиции через наследование Наследование от связанных классов ( ромбовидная иерархия )

Иерархия классов Вызов capabilities должен печатать возможности данного транспорта. Летающее средство – летает, плавающее – плавает, простое может управляться даже обезьяной.

CLOS: определение иерархии ;;;базовый класс, потомок standard-object (defclass vehicle () ((name :initarg :name :accessor vehicle-name))) ;;;наследник vehicle (defclass land-vehicle (vehicle) ()) ;;;наследник land-vehicle и floating-vehicle (defclass amphibian-vehicle (land-vehicle floating-vehicle) ()) ;;;остальные классы аналогично ;;;... ;;;обобщенная функция, не привязанная к классу (defgeneric capabilities (vh))

Экземпляры классов (defparameter t-90 (make-instance 'armour :name "T-90")) (defparameter my-bicycle (make-instance 'bicycle :name "Bicycle")) (defparameter il-86 (make-instance 'plane :name "Il-86"))

Методы функции capabilities (defmethod capabilities ((vh vehicle)) (format t "~a allows to:~%" (vehicle-name vh))) (defmethod capabilities ((vh flying-vehicle)) (call-next-method) (format t "fly~%")) (defmethod capabilities ((vh land-vehicle)) (call-next-method) (format t "move on land~%")) (defmethod capabilities ((vh floating-vehicle)) (call-next-method) (format t "sail~%")) (defmethod capabilities ((vh simple-vehicle)) (call-next-method) (format t "be driven by anyone~%")) ;;что напечатается? (capabilities t-90)

Поведение call-next-method (capabilities t-90) >> T-90 allows to:(3) sail(2) move on land(1) (capabilities my-bicycle) >> Bicycle allows to: be driven by anyone move on land Алгоритм диспетчеризации при вызове : выбираются все подходящие методы выбранные методы упорядочиваются в порядке обхода в ширину вызывается наиболее специфичный метод call-next-method вызывает следующий метод по порядку ( не обязательно метод суперкласса )

Особенности методов в иерархии с множественным наследованием При вызове call-next-method метод может : опираться на общие правила, определяемые контрактом обобщенной функции ; опираться на тот факт, что методы суперклассов будут вызваны позже него ; считать, что всегда можно вызвать call-next-method; на корень иерархии правило не распространяется. При проектировании иерархии следует : при использовании комбинирования методов (call-next- method) определять методы для базового класса иерархии ( даже если они пустые ); делать все методы коммутативными ( по крайней мере для несвязанных наследованием классов ); без необходимости избегать наследования в ромбовидной форме ( хотя бояться его тоже не надо ).

Классическая задача Есть иерархия фигур ( бизнес - логика ). Есть несколько системных представлений фигур для разный графических фреймворков : SWT, Swing, MySuperFramework. Задача : Сделать, чтобы разные фигуры могли рисоваться в разных фреймворках. Проблема : Решение « в лоб » - иерархия с множественным наследованием из классов типа SWTRectangle

Классическое решение : мост Проблема : Мы вынуждены довольствоваться минимальным базисом для всех фигур, хотя знаем, что MySuperFramework умеет рисовать еще и круги. ( про SWT и Swing мы этого не знаем, потому как настоящие ковбои документацию не читают ) Хотим иметь специальный метод draw для Circle + MySuperShape, не ломая при этом всей иерархии.

Другая задача : идем кататься !

CLOS: определение иерархии (defclass driver () ((name :initarg :name :accessor driver-name))) (defclass human-driver (driver) ()) (defclass pilot-driver (human-driver) ()) (defclass animal-driver (driver) ()) ;;;vehicle об этом может ничего не знать (defgeneric ride (dr vh))

Экземпляры классов (defparameter monkey (make-instance 'animal-driver :name "Monkey")) (defparameter anonymous (make-instance 'human-driver :name "Anonymous")) (defparameter pirx (make-instance 'pilot-driver :name "Pirx"))

Поехали ! ;;;метод по умолчанию (defmethod ride ((dr driver) (vh vehicle)) (format t "~a rides ~a~%" (driver-name dr) (vehicle-name vh))) ;;;научили обезьяну кататься на велосипеде (defmethod ride ((dr animal-driver) (vh simple-vehicle)) (format t "~a is smart and rides ~a~%" (driver-name dr) (vehicle-name vh))) ;;;но не на всем остальном ;;;(менее специфичный метод, чем предыдущий) (defmethod ride ((dr animal-driver) (vh vehicle)) (format t "~a is not smart enough to ride ~a~%" (driver-name dr) (vehicle-name vh)))

А теперь полетели ! ;;;кого попало за штурвал не садим (defmethod ride ((dr driver) (vh flying-vehicle)) (format t "~a requires special training to fly ~a~%" (driver-name dr) (vehicle-name vh))) ;;;а вот пилота - садим (defmethod ride ((dr pilot-driver) (vh flying-vehicle)) (format t "~a flyes on ~a~%" (driver-name dr) (vehicle-name vh)))

Диспетчеризация по двум параметрам в действии (ride monkey my-bicycle) >> Monkey is smart and rides Bicycle (ride monkey t-90) >> Monkey is not smart enough to ride T-90 (ride anonymous il-86) >> Anonymous requires special training to fly Il-86 (ride pirx il-86) >> Pirx flyes on Il-86

Проблемы с приоретизацией (defclass base1 () ()) (defclass derived1 (base1) ()) (defclass base2 () ()) (defclass derived2 (base2) ()) (defparameter p1 (make-instance 'derived1)) (defparameter p2 (make-instance 'derived2)) (defgeneric mix (p1 p2)) (defmethod mix ((p1 derived1) (p2 base2)) (format t "1st param specific")) (defmethod mix ((p1 base1) (p2 derived2)) (format t "2nd param specific")) ;;;что будет напечатано? (mix p1 p2)

Решение проблемы с приоретизацией Если используется комбинирование (call-next-method работает точно также !) и мы соблюдаем все правила ( в т. ч. коммутативность ) – проблем нет. Если не используется комбинирование, то либо не допускаем неоднозначностей, либо считаем, что нам не важно, какой метод будет вызван CLOS в этом случае вызовет какой - то из методов с наибольшей специфичностью. В Clojure требуется явно установить приоритет. Примечание : для множественного наследования проблема также актуальна

Вспомогательные методы : готовим и обслуживаем транспорт ;;методы :before и :after комбинируются сами ;;call-next-method не нужен (defmethod ride :before ((dr driver) (vh land-vehicle)) (format t "Fuel the tank~%")) (defmethod ride :after ((dr driver) (vh land-vehicle)) (format t "Turn on alarm~%")) (defmethod ride :before ((dr driver) (vh floating-vehicle)) (format t "Set sails~%")) (defmethod ride :after ((dr driver) (vh floating-vehicle)) (format t "Take in sails~%")) (defmethod ride :before ((dr driver) (vh flying-vehicle)) (format t "Check parachute~%")) (defmethod ride :after ((dr driver) (vh flying-vehicle)) (format t "Be happy with successful landing~%"))

Грузим в танк боеприпасы и наблюдаем за процессом (defmethod ride :before ((dr driver) (vh armour)) (format t "Load ammunition~%")) (defmethod ride :after ((dr driver) (vh armour)) (format t "Leave vehicle without hurts~%")) (defmethod ride :around ((dr driver) (vh vehicle)) (format t "Start observing the show~%") ;;стандартный паттерн :around-метода: ;;запоминаем результат, и потом его возвращаем ;;call-next-method вызывает то что «завернули» (let ((result (call-next-method))) (format t "Finish observing the show~%") result)) ;;;что будет напечатано? (ride pirx t-90)

Результат поездки в танке (ride pirx t-90) >> Start observing the show Load ammunition Fuel the tank Set sails Pirx rides T-90 Take in sails Turn on alarm Leave vehicle without hurts Finish observing the show

Алгоритм стандартного комбинатора методов Для каждого типа методов стоится упорядоченный по специфичности список ( с учетом наследования по всем управляемым параметрам ) Порядок вызова : 1. :around методы начиная с наиболее специфичного ( явно, через call-next-method) 2. :before методы начиная с наиболее специфичного ( неявно ) 3. основные методы, начиная с наиболее специфичного ( первый неявно, остальные явно, через call-next-method) 4. :after методы начиная с наименее специфичного Замечания по производительности : может быть слишком накладно вычислять комбинацию при каждом вызове комбинация методов зависит только от классов параметров – можно использовать кеширование ( сбрасывая кэш при изменении иерархии или декларации новых методов ) при диспетчеризации по одному параметру можно вычислять комбинацию на стадии декларации классов и методов

Ошибка в проектировании : обезьяна с гранатой (ride monkey t-90) >> Start observing the show Load ammunition Fuel the tank Set sails Monkey is not smart enough to ride T-90 Take in sails Turn on alarm Leave vehicle without hurts Finish observing the show ;;может, надо было заранее проверить насчет обезьяны

Использование вспомогательных методов по назначению :around – единственный, кто может отменить или подменить выполнение всей комбинации методов. Самая естественная его функция – проверка контракта ( пред -, пост - условия ), авторизация :before, :after – операции, не связанные с бизнес - логикой ( блокировки, открытие / закрытие соединений, отложенная инициализация, отладка и профилирование ). Все то же самое можно сделать через :around, но сложнее и опаснее. Основные методы – основная логика Примечание : все вышеперечисленно догмой не является, имеет право на существование любое использование, соблюдающее SoC и KISS

Что все это дает ? Плюсы : За счет обобщенных функций не привязанных к классам и разных форм динамического полиморфизма – широкие возможности по разделению функциональности. Атомарная единица модульности – меньше класса и даже меньше функции ( в обычном понимании ). Это основа для АОП. Механизмы не привязаны к специфике Lisp и вообще динамических языков. Минусы : Дилемма : как всю эту « мелочь » правильно упаковать в разумное число модулей. Принципы проектирования пакетов (Common Closure, Common Reuse, Release-Reuse Equivalency) – в помощь Код из - за раздробленности сложнее читать, требуются удобные IDE, визуализирующие переплетения аспектов Требуется высокая культура программирования, иначе получаем богатые возможности « выстрелить себе в ногу » ( и не только себе )

Полиморфизм в Clojure: определение иерархии ;;;вместо деклараций классов - метки типов (derive ::floating-vehicle ::vehicle) (derive ::land-vehicle ::vehicle) (derive ::amphibian-vehicle ::floating-vehicle) (derive ::amphibian-vehicle ::land-vehicle) (derive ::armour ::amphibian-vehicle) (derive ::simple-vehicle ::vehicle) (derive ::bicycle ::land-vehicle) (derive ::bicycle ::simple-vehicle) (derive ::animal-driver ::driver) (derive ::human-driver ::driver)

Определение обощенных функций и мультиметодов в Clojure ;;;аналог defgeneric в CLOS (defmulti ride ;;;функция вычисления типа (fn [driver vehicle] [(:type driver) (:type vehicle)])) (defmethod ride [::driver ::vehicle] [driver vehicle] (println (:name driver) "rides" (:name vehicle))) (defmethod ride [::animal-driver ::simple-vehicle] [driver vehicle] (println (:name driver) "is smart and rides" (:name vehicle))) (defmethod ride [::animal-driver ::vehicle] [driver vehicle] (println (:name driver) "is not smart enough to ride" (:name vehicle)))

И снова поехали ! (let [monkey {:type ::animal-driver, :name "Monkey"} anonymous {:type ::human-driver, :name "Anonymous"} t-90 {:type ::armour, :name "T-90"} bicycle {:type ::bicycle, :name "Bicycle"}] (ride monkey bicycle) (ride monkey t-90) (ride anonymous t-90)) >> Monkey is smart and rides Bicycle Monkey is not smart enough to ride T-90 Anonymous rides T-90

Отличия от CLOS Плюсы : Полиморфизм не привязан к определенной объектной модели : понятие типа определяет пользователь Работает в т. ч. для объектной модели Java Функция вычисления типа не обязана возвращать тип в иерархии. В качестве типа может быть произвольный объект, который сравнивается с образцом при диспетчеризации Минусы : Нет возможности комбинирование методов Не разрешаются конфликты при множественном наследовании или диспетчеризации по нескольким параметрам