Что такое диспетчеризация в программировании
Перейти к содержимому

Что такое диспетчеризация в программировании

  • автор:

Учебники. Программирование для начинающих.

Programm.ws — это сайт, на котором вы можете почитать литературу по языкам программирования , а так-же посмотреть примеры работающих программ на С++, ассемблере, паскале и много другого..

Программирование — в обычном понимании, это процесс создания компьютерных программ.
В узком смысле (так называемое кодирование) под программированием понимается написание инструкций — программ — на конкретном языке программирования (часто по уже имеющемуся алгоритму — плану, методу решения поставленной задачи). Соответственно, люди, которые этим занимаются, называются программистами (на профессиональном жаргоне — кодерами), а те, кто разрабатывает алгоритмы — алгоритмистами, специалистами предметной области, математиками.
В более широком смысле под программированием понимают весь спектр деятельности, связанный с созданием и поддержанием в рабочем состоянии программ — программного обеспечения ЭВМ. Более точен современный термин — «программная инженерия» (также иначе «инженерия ПО»). Сюда входят анализ и постановка задачи, проектирование программы, построение алгоритмов, разработка структур данных, написание текстов программ, отладка и тестирование программы (испытания программы), документирование, настройка (конфигурирование), доработка и сопровождение.

Cамоучитель по Java

Глава 12. Обработка событий

Диспетчеризация событий

Если вам понадобится обработать просто действие мыши, не важно, нажатие это, перемещение или еще что-нибудь, то придется включать эту обработку во все семь методов двух классов-слушателей событий мыши.

Эту работу можно облегчить, выполнив обработку не в слушателе, а на более ранней стадии. Дело в том, что прежде чем событие дойдет до слушателя, оно обрабатывается несколькими методами.

Чтобы в компоненте произошло событие AWT, должно быть выполнено хотя бы одно из двух условий: к компоненту присоединен слушатель или в конструкторе компонента определена возможность появления события методом enableEvents (). В аргументе этого метода через операцию побитового сложения перечисляются константы класса AWTEvent, задающие события, которые могут произойти в компоненте, например:

AWTEvent.MOUSE_EVENT_MASK I AWTEvent.KEY_EVENT_MASK)

При появлении события создается объект соответствующего класса xxxEvent. Метод dispatchEvent () определяет, где появилось событие — в компоненте или одном из его подкомпонентов, — и передает объект-событие методу processEvent <) компонента-источника.

Метод processEvent о определяет тип события и передает его специализированному методу processxxxEvent о. Вот начало этого метода:

protected void processEvent(AWTEvent e)

if (e instanceof FocusEvent)

>else if (e instanceof MouseEvent)

break; > >else if (e instanceof KeyEvent)

Затем в дело вступает специализированный метод, например, processKeyEvent о. Он-то и передает объект-событие слушателю. Вот исходный текст этого метода:

protected void processKeyEvent(KeyEvent e)

KeyListener listener = keyListener;

if (listener != null)< int

case KeyEvent.KEYJTYPED: listener.keyTyped(e);

case KeyEvent.KEY_PRESSED: listener.keyPressed(e);

case KeyEvent.KEY_RELEASED: listener.keyReleased(e);

Из этого описания видно, что если вы хотите обработать любое событие типа AWTEvent, то вам надо переопределить метод processEvent (), а если более конкретное событие, например, событие клавиатуры, — переопределить более конкретный метод processKeyEvent о. Если вы не переопределяете весь метод целиком, то не забудьте в конце обратиться к методу суперкласса, например, super.processKeyEvent(e);

He забывайте обращаться к методу processXxxEvent() суперкласса.

В следующей главе мы применим такое переопределение в листинге 13.2 для вызова всплывающего меню.

Диспетчеризация методов в Swift: что это и как работает

Сегодня мы поговорим про механизм диспетчеризации методов. Он существует в разных языках, но мы сделаем акцент именно на Swift.

Обложка поста Диспетчеризация методов в Swift: что это и как работает

Сегодня мы поговорим про механизм диспетчеризации методов. Он существует в разных языках, но мы сделаем акцент именно на Swift. Разработчики часто имеют дело с данным механизмом. Даже не подозревая этого, каждый написанный нами метод ведёт к этому процессу. Данная статья подойдёт абсолютно всем уровням, здесь я не хочу закапываться в глубину байт-кода и смотреть что там происходит, но всё же хочу немного уйти под капот этого механизма. А также это очень частый вопрос на собеседованиях. Некоторые смелые компании спрашивают диспетчеризацию даже у Junior разработчиков.

Всем привет, меня зовут Макс Нечаев, я тех-лид и iOS разработчик в крупном фудтех стартапе на арабском рынке (Snoonu).

Что такое диспетчеризация простым языком? Диспетчеризация — это процесс, результатом которого является выбор имплементации метода. Хорошо, давайте попробуем проще. Диспетчеризация ловит вызов метода и выбирает его реализацию.

Например, ваш родственник просит сходить вас в магазин. Исходя из контекста разговора вы понимаете, что вам нужно сходить или в продуктовый за молоком, или в мясной за стейком, или в хозтовары за моющим средством. Процесс, в начале которого вы получили задачу сходить в “магазин” и до момента, когда вы поняли, в какой именно “магазин” нужно идти и будет являться диспетчеризацией.

С первого взгляда может показаться непонятным, где именно подобное может использоваться. Но стоит не забывать, что в Swift у нас есть наследование и полиморфизм. То есть методы могут переопределяться, могут определяться в протоколах через расширения и т.д.

Диспетчеризация методов в Swift: что это и как работает 1

Всего у нас три типа диспетчеризации

Первая: Static dispatch — статическая.

Вторая: Table dispatch — динамическая, которая делится на две:

Третья: Message dispatch

Далее предлагаю рассмотреть все три более подробно.

Static dispatch

Статическая диспетчеризация самая быстрая. Зачастую разработчики стараются привести именно к этому типу вызова методов. Почему так? Потому что уже на этапе комплиляции известно, какую имплементацию метода нужно использовать.

Статическая диспетчеризация используется в методах структур (value типов), final классов, а также в extension классов и протоколов. Во всех случаях метод не может быть переопределен никак, и выбор его имплементации всегда однозначен.

Примеры методов со Static dispatch:

final class SampleFinalClass < func sayHello() < print("Hello!") >> struct SampleStruct < func sayHello() < print("Hello!") >> extension SampleStruct < func someFunc() < print("Hello!") >> 

Преимущества: самая быстра диспетчеризация.

Недостатки: отсутствие полиморфизма и наследия.

Table dispatch

Табличная диспетчеризация (динамическая) работает на таблицах, как и следует из ее названия. Здесь стоит понимать, что на этапе компиляции формируются таблицы с адресами методов определенной сущности. Но уже именно в runtime происходит поиск нужного метода по адресам.

Так как это происходит именно в рантайме, думаю, вы понимаете, что здесь скорость диспетчеризации может повлиять на производительность. Но несмотря на это, есть и явные плюсы Table dispatch. Далее мы рассмотрим два подвида этой диспетчеризации.

Virtual Table

Если говорить про Virtual Table Dispatch, то скорее всего мы имеем ввиду работу с классами, с наследованием и переопределением методов.

Сейчас я попробую на простом примере рассказать как работает этот тип диспетчеризации. Сначала я сделаю это текстом, затем возьму в пример небольшую реализацию и пройдем по ней для закрепления.

Для каждого класса создается своя виртуальная таблица с адресами методов. Если в классе наследнике методы не переопределяются, то адреса в его таблице ведут на методы из родительского класса, если же мы меняем, переопределяем или добавляем методы, то у наследника будут новые адреса на имплементацию методов.

Давайте создадим два класса, отец (Father) и ребенок (Child). Причем ребенок должен быть наследником класса отца. У отца будут методы: есть, гулять, спать, работать.

class Father < func eat() <>func walk() <> func sleep() <> func work() <> > class Child: Father < >let child = Child() child.eat() 

Смотрите, это очень интересный момент. У каждого класса своя виртуальная таблица. Но при это когда у child мы вызываем метод eat(), вызывается имплементация из класса Father. Почему так? Потому что адреса методов в виртуальной таблице ребенка ведут на те же адреса, что и в виртуальной таблице класса отца.

Вопрос на подумать. Как же сделать так, чтобы у виртуальной таблицы класса Child были свои уникальные адреса на имплементацию этих методов?

class Child: Father < override func eat() <>override func walk() <> override func sleep() <> override func work() <> > 

Мы переопределяем методы, теперь Virtual Table Dispatch у класса Child будет вызывать именно его имплементацию методов, а не класса Father.

Witness Table

Когда мы говорим про Witness Table Dispatch, то здесь скорее всего мы имеем ввиду работу с протоколами. На уровне теории, для класса, который реализует протокол, а именно для методов этого протокола, создается отдельная таблица. Если класс реализует несколько протоколов, то создается несколько Witness таблиц. Основное отличие от Virtual table, что здесь нет наследования.

Давайте попробуем разобраться на примере. Создадим два класса: брат и сестра (Brother / Sister). Также создадим протокол GameProtocol, который обязует класс реализовать метод func buyGame().

Для каждого класса создается своя таблица со своим адресом реализации метода. Здесь не может быть одного адреса, даже не смотря на то, что метод один и протокол один. Так как каждый класс может реализовать его по-своему.

protocol GameProtocol < func buyGame() >class Brother: GameProtocol < func buyGame() < print("AssassinsCreed") >> class Sister: GameProtocol < func buyGame() < print("Barbie") >> 

Каждый класс имеет свою Witness Table и сам решает, как ему реализовать протокол.

Подведем небольшой итог Table dispatch.

Преимущества: наличие полиморфизма (гибкости), возможность наследования

Недостатки: медленнее, чем статическая диспетчеризация

Message dispatch

Динамичный вид диспетчеризации, самый медленный и связан с Objective-C. Он полностью работает в runtime. Для работы с Message Dispatch методам добавляется модификатор @objc dynamic. Как он работает под капотом? В отличии от табличной диспетчеризации, Message Dispatch ищет реализацию метода в дочернем классе, затем по ссылке переходит в таблицу родительского класса и ищет там, если и там нет реализации, переходит на уровень ниже. Таким образом создается длинная цепочка поиска реализации метода от самого верхнего класса, до самого нижнего класса, именно это и делает данный тип диспатча самым медленным.

Message Dispatch лежит в основе KVO и KVC. Так как Message Dispatch работает в runtime, появляется возможность подменить реализацию методов — это называется Method Swizzling. Method Swizzling позволяет подменить метод другим в runtime, при этом оригинальная имплементация остается доступной.

Преимущества: KVO и KVC, Method Swizzling

Недостатки: самая медленная диспетчеризация

Итог

В конце концов хочется сказать, что данная тема не самая ключевая в работе iOS разработчика, но при этом рекомендуется к пониманию. Более того, на собеседованиях вас часто будут спрашивать и гонять по диспетчеризации. Данных знаний вам должно хватить, чтобы ответить на 95% всех вопросов по теме. Более того, прилагаю вам такую схемку, которую можно держать перед глазами, она помогает очень просто и быстро понять, какая и где диспетчеризация.

Динамическая диспетчеризация — PHP: Полиморфизм

Для лучшего понимания ООП и конкретно полиморфизма, полезно разбираться в том, как оно работает внутри языка. Для этого нужно познакомиться с понятием динамическая диспетчеризация.

Когда в коде встречаются обычные функции, то всегда можно однозначно сказать где они определены. Для этого достаточно посмотреть импорты.

 use function Funct\union; union($data1, $data2); 

С методами все сложнее. Глядя на вызов метода, нельзя однозначно сказать, откуда он пришёл. Это зависит от типа объекта у которого он вызван. Полиморфизм подразумевает подмену объектов, а значит одна и та же строчка кода может вызывать разные методы (но имеющие одинаковые имена), в зависимости от пришедшего объекта.

Полиморфизм в PHP, как и во многих других языках, реализуется с помощью динамической диспетчеризации. Это механизм, который занимается выбором необходимой реализации метода. Рассмотрим его работу на примере. Ниже показана функция, которая ожидает что на вход ей передаётся объект с методом getName() и печатает его на экран.

 function printName($obj)  echo $obj->getName(); > printName(new User(['name' => 'Mike'])); 
  1. new User([‘name’ => ‘Mike’]) – С точки зрения PHP-программиста, она создает объект, но внутри PHP объектов нет. Это обычная структура данных на Си. Она хранит в себе данные объекта и некоторую метаинформацию. К ней, например, относится текущий класс. Именно так PHP определяет, какой объект к какому классу относится. С другой стороны, в этой структуре нет методов. Они хранятся отдельно.
  2. $obj->getName() – Вызов метода, запускает механизм диспетчеризации. Первым делом он выясняет какой класс у данного объекта. Затем проверяет список методов относящихся к этому классу и ищет среди них getName() . Если метод существует, то он вызывает именно его.

Технически этот процесс можно представить так:

 // Что происходит во время вызова: $obj->getName() => callMethod($obj, 'getName') function callMethod($this, $methodName, $args) // функция-диспетчер  $className = $this['className']; // Специальная функция, которая хранит список классов и связанных с ними методов $methods = getClassMethods($className); // Берём нужный метод и вызываем его $method = $methods[$methodName]; if ($method)  return $method($this, . $args); > else if (isset($methods['__call']))  // Если метод не найден, но есть магический метод __call то вызываем его return $methods['__call']($this, . $args); > throw new \Exception('No method error'); > 

Этот код содержит очень интересные детали. Во-первых, методы это логическое понятие. Внутри языка это обычные функции. Во-вторых, список классов и методов хранится в обычной структуре данных, называемой виртуальной таблицей. Именно по ней ведет поиск функция getClassMethods . В-третьих, $this — это наша исходная структура данных (мы получили ее когда делали new User() ), которая передается первым параметром. Другими словами, $this это синтаксически скрытый, первый параметр функций, называемых методами.

Среди популярных языков, есть как минимум один, который не скрывает этот факт. В Python первый параметр любого метода называется self (который играет ту же роль, что и $this в PHP). Создатели языка резонно посчитали, что его не стоит прятать от программистов, иначе у них возникает ощущение волшебства происходящего.

class Shark: def __init__(self, name): self.name = name def swim(self): # Reference the name print(self.name + " is swimming.") def be_awesome(self): # Reference the name print(self.name + " is being awesome.") 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Что такое диспетчеризация в программировании

Автор: Дэвид Мертц (David Mertz)
Перевод: Intersoft Lab
Авторские права: market@iso.ru

Обобщение полиморфизма с помощью мультиметодов

Во многом универсальность объектно-ориентрованного программирования (ООП) возможна благодаря полиморфизму: в надлежащем контексте объекты разных видов могут вести себя различным образом. Однако большая часть ООП — это единичная диспетчеризация, то есть единственный выделенный объект определяет, какую ветвь кода избрать. Концептуально более общий подход — позволить всем аргументам, передаваемым в функцию/метод, определять ее специализацию. В этой статье рассматривается реализация множественной диспетчеризации в Python и показано, как это улучшает программы.

Что такое полиморфизм?

Большинство программистов, использующих полиморфизм — на Python или других языках объектно-ориентированного программирования -, находят ему весьма практическое и конкретное применение. Возможно, наиболее общий случай использования полиморфизма — это создание семейства объектов, которые придерживаются общего протокола. В Python это обычно просто вопрос нерегламентированного полиморфизма; в других языках чаще объявляются формальные интерфейсы, и/или эти семейства обладают общим предком.

Например, существует множество функций, которые работают с объектами, «подобными файлам», где это подобие файлам определяется просто посредством поддержания нескольких методов, как .read() , .readlines() и, возможно, .seek() . Функция, как read_app_data() , может принимать аргумент src — когда мы вызовем эту функцию, мы, возможно, решим передать ей локальный файл, объект urllib , объект cStringIO или некий объект, определенный пользователем, который разрешает этой функции вызывать src.read() . Каждый тип объекта равнозначен с точки зрения того, как он функционирует в read_app_data() .

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

Листинг 1. Процедурный выбор ветвей кода по типу объекта

 . bind 'src' in some manner. if >: read_from_file(src) elif >: read_from_url(src) elif >: read_from_stringio(src) . etc. 

Организовав поддержку общих методов объектами различных типов, мы перемещаем решение о диспетчеризации в объекты из явного условного блока. Просматривая дерево наследования, данный объект src узнает, какие блоки кода ему нужно вызывать. Однако, по-прежнему происходит неявное переключение, но по типу объекта src .

Объект src привилегирован по отношению к любым аргументам, передаваемым в его методы. Из-за синтаксиса ООП эта привилегированность кажется неизбежной, но на самом деле это не так. Во многих случаях процедурное переключение просто переносится в тела методов классов. Например, мы могли бы реализовать совместимые по протоколу классы Foo и Bar следующим образом:

Листинг 2. Реализация метода .meth() с помощью Foo и Bar

 class Foo: def meth(self, arg): if >: . FooFoo code block. elif >: . FooBar code block. class Bar: def meth(self, arg): if >: . BarFoo code block. elif >: . BarBar code block. # Function to utilize Foo/Bar single-dispatch polymorphism def x_with_y(x, y): if > and >: x.meth(y) else: raise TypeError,"x, y must be either Foo's or Bar's" 

Имеется пять различных ветвей/блоков кода, которые могут выполняться при вызове x_with_y() . Если типы x и y не подходят, возбуждается исключение (разумеется, вы могли бы сделать что-нибуль другое). Но, предполагая, что с типами все в порядке, ветвь кода выбирается сначала посредством полиморфной диспетчеризации, а затем посредством процедурного переключения. Кроме того, переключения внутри определений Foo.meth() и Bar.meth() в значительной степени эквивалентны. Полиморфизм — в разновидности с единичной диспетчеризацией — решает лишь половину задачи.

Полная реализация полиморфизма

В случае полиморфизма с единичной диспетчеризацией выделяется объект, который «владеет» методом. Синтаксически в Python его выделяют, располагая его имя перед точкой — все, что следует за точкой: имя метода и левая скобка — просто аргумент. Но семантически этот объект является особенным при использовании дерева наследования для выбора метода.

А что если бы мы обрабатывали особым образом не один объект, а позволили бы каждому объекту, задействованному в блоке кода, участвовать в выборе ветви выполнения? Например, мы могли бы выразить наше пятистороннее переключение более симметрично:

Листинг 3. Множественная диспетчеризация Foo и Bar

 x_with_y = Dispatch([((object, object), >)]) x_with_y.add_rule((Foo,Foo), >) x_with_y.add_rule((Foo,Bar), >) x_with_y.add_rule((Bar,Foo), >) x_with_y.add_rule((Bar,Bar), >) #. call the function x_with_y() using some arguments. x_with_y(something, otherthing) 

Я думаю, что эта симметричность полиморфной диспетчеризации по множеству аргументов гораздо более элегантна, чем предшествующий стиль. Кроме того, этот стиль позволяет документировать одинаковую роль этих двух объектов, задействованных в определении подходящей ветви кода.

Стандартный Python не разрешает конфигурировать этот тип множественной диспетчеризации; но, к счастью, вы можете сделать это, воспользовавшись написанным мною модулем multimethods . См. Ресурсы, чтобы скачать этот модуль отдельно или в составе утилит Gnosis. После того, как вы установили multimethods , все, что от вас требуется — включить в начало своего приложения следующую строку:

from multimethods import Dispatch

«Мультиметоды», как правило, это синоним множественной диспетчеризации; но термин мультиметод предполагает конкретную функциональную/объектную реализацию более абстрактной концепции множественной диспетчеризации.

Экземпляр Dispatch — это вызываемый объект, его можно конфигурировать с любым желаемым количеством правил. К тому же, можно использовать метод Dispatch.remove_rule() , чтобы удалять правила; благодаря этому множественная диспетчеризация с использованием multimethods становится несколько более динамичной, чем статическая иерархия классов (но вы также можете совершить некие замысловатые действия с классами Python во время исполнения). Также заметьте, экземпляр Dispatch может принимать переменное число аргументов; сопоставление выполняется сначала по числу аргументов, затем по их типам. Если экземпляр Dispatch вызывается с любым шаблоном, который не определен в правиле, возбуждается TypeError . Инициализация x_with_y() с запасным шаблоном (object,object) необязательна, если вы просто хотите, чтобы в неопределенных ситуациях возбуждалось исключение.

Каждый кортеж (pattern,function) , перечисленный в инициализации Dispatch , просто передается далее в метод .add_rule() ; это исключительно вопрос удобства программирования — устанавливать правила при инициализации или позже (можно комбинировать подходы, как в предшествующем примере). При вызове функции из диспетчера аргументы, используемые при вызове, передаются диспетчеру; вы должны обеспечить, чтобы функция, которую вы используете, могла принять то число аргументом, с которым она сопоставляется. Например, приведенные ниже вызовы эквиваленты:

Листинг 4. Явный вызов и вызов функции при диспетчеризации

 # Define function, classes, objects def func(a,b): print "The X is", a, "the Y is", b class X(object): pass class Y(object): pass x, y = X(), Y() # Explicit call to func with args func(x,y) # Dispatched call to func on args from multimethods import Dispatch dispatch = Dispatch() dispatch.add_rule((X,Y), func) dispatch(x,y) # resolves to 'func(x,y)' 

Очевидно, что если вы знаете типы x и y во время проектирования, алгоритм задания диспетчера — просто накладные расходы. Но то же ограничение справедливо и для полиморфизма — он удобен, лишь когда вы не можете ограничить объект единственным типом для каждой ветви исполнения.

Улучшение наследования

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

Листинг 5. Наследование для расширения возможностей

 # Base classes class Circle(Shape): def combine_with_circle(self, circle): . def combine_with_square(self, square): . class Square(Shape): def combine_with_circle(self, circle): . def combine_with_square(self, square): . # Enhancing base with triangle shape class Triangle(Shape): def combine_with_circle(self, circle): . def combine_with_square(self, square): . def combine_with_triangle(self, triangle): . class NewCircle(Circle): def combine_with_triangle(self, triangle): . class NewSquare(Square): def combine_with_triangle(self, triangle): . # Can optionally use original class names in new context Circle, Square = NewCircle, NewSquare # Use the classes in application c, t, s = Circle(. ), Triangle(. ), Square(. ) newshape1 = c.combine_with_triangle(t) newshape2 = s.combine_with_circle(c) # discover 'x' of unknown type, then combine with 't' if isinstance(x, Triangle): new3 = t.combine_with_triangle(x) elif isinstance(x, Square): new3 = t.combine_with_square(x) elif isinstance(x, Circle): new3 = t.combine_with_circle(x) 

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

Напротив, метод множественной диспетчеризации более прост:

Листинг 6. Мультиметоды для расширения возможностей

 # Base rules (stipulate combination is order independent) class Circle(Shape): pass class Square(Shape): pass def circle_with_square(circle, square): . def circle_with_circle(circle, circle): . def square_with_square(square, square): . combine = Dispatch() combine.add_rule((Circle, Square), circle_with_square) combine.add_rule((Circle, Circle), circle_with_circle) combine.add_rule((Square, Square), square_with_square) combine.add_rule((Square, Circle), lambda s,c: circle_with_square(c,s)) # Enhancing base with triangle shape class Triangle(Shape): pass def triangle_with_triangle(triangle, triangle): . def triangle_with_circle(triangle, circle): . def triangle_with_square(triangle, square): . combine.add_rule((Triangle,Triangle), triangle_with_triangle) combine.add_rule((Triangle,Circle), triangle_with_circle) combine.add_rule((Triangle,Square), triangle_with_square) combine.add_rule((Circle,Triangle), lambda c,t: triangle_with_circle(t,c)) combine.add_rule((Square,Triangle), lambda s,t: triangle_with_square(t,s)) # Use the rules in application c, t, s = Circle(. ), Triangle(. ), Square(. ) newshape1 = combine(c, t)[0] newshape2 = combine(s, c)[0] # discover 'x' of unknown type, then combine with 't' newshape3 = combine(t, x)[0] 

Определение новых правил (и поддержка функций/методов) в значительной степени эквивалентны. Но огромное преимущество стиля множественной диспетчеризации — это цельность, с помощью которой вы комбинировать фигуры неизвестных типов. Вместо того, чтобы возвращаться к явным (и длинным) условным блокам, определения правил автоматически решают эти вопросы. Что еще лучше, все комбинирование выполняется одним вызовом combine() , а не с помощью «зверинца» из разных комбинирующих методов.

Передача диспетчеризации

Не испытывая необходимости больше думать о диспетчеризации, класс multimethods.Dispatch будет выбирать «наилучшее совпадение» для данного обращения к диспетчеру. Однако, иногда стоит заметить, что «лучшее» не значит «единственное». То есть, обращение к dispatch(foo,bar) может давать точное совпадение с правилом (Foo,Bar) — но оно также может задавать менее точное совпадение (не промах!) для (FooParent,BarParent) . Точно так, как иногда вы хотите вызывать методы базовых классов в методе производного класса, вы также иногда желаете вызывать менее специфические правила в диспетчере.

Модуль multimethods позволяет задавать вызовы менее специфических правил как грубо, так и с тонкой настройкой. На грубом уровне, обычно вы просто хотите автоматически вызывать менее специфичное правило в начале, либо в конце выполнения блока кода. Подобным образом вы практически всегда вызываете метод надкласса в начале, либо в конце тела метода потомка. Общий вариант начального/конечного вызова менее специфичных методов может быть задан просто как часть правила. Например:

Листинг 7. Автоматическое воспроизведение диспетчеризации

 class General(object): pass class Between(General): pass class Specific(Between): pass dispatch = Dispatch() dispatch.add_rule((General,), lambda _:"Gen", AT_END) dispatch.add_rule((Between,), lambda _:"Betw", AT_END) dispatch.add_rule((Specific,), lambda _:"Specif", AT_END) dispatch(General()) # Result: ['Gen'] dispatch(Specific()) # Result: ['Specif', 'Betw', 'Gen'] 

Разумеется, в некоторых ситуациях (как для правила (General) ) менее специфичное правило отсутствует. Для обеспечения единообразия, однако, каждое обращение к диспетчеру возвращает список значений из всех функций, которым передается управление таким образом. Если в правиле не определены ни AT_END , ни AT_START , распространение вызовов не производится (и возвращается список из одного элемента). Этим объясняется индекс [0] в примере с фигурами, который, вероятно, кажется загадочным .

Для тонкой настройки распространения вызовов применяется метод диспетчера .next_method() . Чтобы задать распространение вызовов вручную, нужно использовать для определения правил метод .add_dispatchable() , а не метод .add_rule() . Кроме того, диспетчеризованные функции сами должны принимать аргумент dispatch . При вызове диспетчера вы либо должны передать аргумент, задающий диспетчер, либо вы можете воспользоваться вспомогательным методом .with_dispatch() . Например:

Листинг 8. Программирование с ручной передачей

 def do_between(x, dispatch): print "do some initial stuff" val = dispatch.next_method() # return simple value of up-call print "do some followup stuff" return "My return value" foo = Foo() import multimethods multi = multimethods.Dispatch() multi.add_dispatchable((Foo,), do_between) multi.with_dispatch(foo) # Or: multi(foo, multi) 

Вызов менее специфичных мультиметодов вручную может оказаться запутанным — примерно так же, как и обращение к методам базовых классов. Чтобы эти вопросы стали управляемыми, обращение к .next_method() всегда возвращает простой результат вызова верхнего уровня — если вы хотите собрать такие результаты в список, как тот, что создает аргумент AT_END , вам нужно добавлять и обрабатывать те величины, которые вы считаете уместными. Наиболее общий вариант использования, однако — выполнение последовательности связанных инициализаций; в этом случае возвращаемые величины обычно неважны.

Замечания выполнении в многонитевой среде

Стоит привести краткое замечание, пока читатель не столкнулся с проблемой. Из-за необходимости сохранения состояния для отслеживания, какие (последовательно менее специфичные) правила вызывались, диспетчер не является нитебезопасным. Если нужно использовать диспетчер в многонитевой среде, необходимо «клонировать» его для каждой нити. Это ненакладно с точки зрения ресурсов: памяти и процессора, так что клонирование диспетчеров не вызывает существенных неудобств. Например, предположим, что функция могла бы вызываться из разных нитей; вы можете написать:

Листинг 9. Клонирование для безопасности нити

 def threadable_dispatch(dispatcher, other, arguments) dispatcher = dispatcher.clone() #. do setup activities. dispatcher(some, rule, pattern) #. do other stuff. 

Если внутри threadable_dispatch() не запускаются новые нити, все нормально.

Вам потребуется некоторое время, чтобы освоиться с идей множественной диспетчеризации, даже — или особенно — если вы весьма опытны в объектно-ориентированном программировании. Но после того, как вы немного с ней поэкспериментируете, вероятно, вы обнаружите, что множественная диспетчеризация обобщает и усиливает преимущества, которыми ООП обладает прежде всего над процедурным программированием.

Ресурсы

  • Вы можете получить multimethods как отдельный модуль, либо как часть пакета Gnosis Utilities.
  • Gnosis Utilities выходит как Питоновский пакет distutils .
  • Другие языки реализовали множественную диспетчеризацию либо в самом языке, либо в библиотеках. Например, MultiJava — расширенный набор Java, который реализует множественную диспетчеризацию.
  • CLOS и Dylan используют множественную диспетчеризацию в качестве базиса своей системы ООП. Возможно, «Обсуждение подхода Dylan» (a discussion of Dylan’s mechanism) покажется вам интересным.
  • В Perl есть модуль под названием Class::Multimethods , предназначенный для реализации множественной диспетчеризации (и, по-видимому, предполагается, что в Perl 6 эта концепция будет более глубоко встроена в язык). Дэмиан Конуэей рассматривает этот модуль (Damian Conway discusses his module).
  • Ресурсы для разработчиков Linux в зоне Linux developerWorks.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *