Нетрадиционно ориентированная диспетчеризация

Table of Contents

Table of Contents

Abstract

Основным вкладом объектно-ориентированного подхода в программирование является специальный (ad-hoc) полиморфизм, реализуемый с помощью динамической диспетчеризации, когда эффективный метод выбирается на основании одного (this) или нескольких (в языках с поддержкой обобщенных функций) параметров.

В объектно-ориентированных языках методы (в том числе и переопределенные) наследуются по цепочке классов или объектов, выбор метода обусловлен иерархией наследования аргументов.

Объектный подход

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

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

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

Одним из решений этой проблемы является использование механизма явной диспетчеризации: объект, получивший сообщение, пересылает его на другой объект на основе некоторых произвольных критериев. Популярный пример такого подхода - паттерн проектирования State (Состояние)

Недостатком такого подхода является то, что он вводит проблему идентификации объекта - this больше не является получателем сообщения, для которого вызывается эффективный метод. Эта ситуация также иногда называется проблемой шизофрении объекта (object schizophrenia problem).

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

Smalltalk и CLOS, среди прочего, позволяют изменить класс объекта, и следовательно его таблицу методов: в Smalltalk-е можно заменить обьект obj-1 на объект obj-2 так, что все ссылки на obj-1 будут указывать на obj-2 при сохранении идентификации obj-1. В CLOS можно непосредственно изменить класс объекта не затрагивая его идентификации.

В принципе, эти операции могут быть использованы для реализации State-like паттернов. Однако, как правило, не рекомендуется их использовать: в Smalltalk не проверяется совместимость схем объектов, так что это может фактически привести к сбою системы (с задержкой) при неправильном использовании. Common Lisp спецификация также явно не рекомендует использование change-class: когда метод выбирается в зависимости от класса конкретного объекта, изменение класса этого объекта в то время как метод выполняется может привести к "неопределенному результату" из-за оптимизаций, которые компилятору Common Lisp разрешено выполнять. То же касается и SmallTalk - если экземляр объекта меняет свой класс - не определено, будет ли в настоящем времени выполнения видима старая или новая таблица диспетчеризации.

Фильтрующая диспетчеризация

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

Фильтрующие функции могут быть использованы для диспетчеризации методов, основанной на состоянии переданных аргументов и позволяет выразить State-like паттерны без "проблемы шизофрении объекта"

Субьектная диспетчеризация

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

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

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

В этом случае целесообразно объединять инварианты поведения полиморфных методов разделяя их на "роли", ассоциированные с субьектами, вызывающими сервис.

В современных наиболее распространенных объектно-ориентированных языках следовые количества субьектно-ориентированной диспетчеризации находятся в объявлениях private, protected и public членов класса.

Контекстная диспетчеризация

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

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

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

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

Используя такой вид полиморфизма рационально выделять типичные инварианты поведения в так называемые "слои" ("layers") и в зависимости от изменений окружения активировать те из них, которые соответствуют текущему состоянию окружения.

MVС Considered Harmful

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

Можно легко предствить себе, что объекты сами знают как себя вести, т.е. реагировать на сообщения извне. Однако, когда программы становятся более сложными, код для отображения (View) объектов, как правило, уже не содержится в классе, потому что нужно иметь разные отображения для одного и того же объекта, часто в одно и то же время.

Поэтому такой код отделяется в View-объект, который должен быть проинформирован об изменениях в Model-объекте, что приводит к вариантам архитектуры Model-View-Controller первоначально введенной в SmallTalk.

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

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

Выбор эффективного метода

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

Субьектно-ориентированная диспетчеризация практически не представлена, что приводит к большому количеству кода, осуществляющего проверку привелегий. Этот код обычно размазан по всему приложению или, в лучшем случае, вынесен в отдельную "систему прав" представляющую собой самодельную вариацию на тему общеизвестных идей (ACL, RBAC, MAC, или DAC). Как правило этот код весьма сложно поддерживать, а в большинстве случаев он еще и является точкой атаки злоумышленников.

Контекстно-ориентированная диспетчеризация отсутствует во всех известных автору используемых языках за исключением Common Lisp - там она доступна при использовании библиотеки, расширяющей язык за счет использования метаобъектного протокола. Более подробные сведения можно почерпнуть из работ Pascal Costanza.

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

Несмотря на серьезные проблемы с диспетчеризацией в большинстве широко используемых языков, в Common Lisp существует библиотеки поддерживающие на уровне языка контекстно-ориентированную и фильтрующую диспетчеризацию, опирающуюся на использование метаобъектного протокола и CLOS. Эта библиотека доступна в исходных кодах и может служить примером разработки и внедрения актуальных возможностей диспетчеризации и в других языках.

Яндекс.Метрика
Home