Рассмотрим переадресацию методов объекта другому объекту.
Пусть объект Б переадресует объекту А ему вызов метода m1.
Встает вопрос - а что происходит с this?
Есть три варианта:
1. Форвард - this Б не доступен
2. Делегация - this Б передается через аргументы (неявные, см. ниже).
3. Hаследование - Б эквивалентно А (this равны).
Форвард это тоже самое что обычный вызов одного объекта из другого
m1() { Б.m1 }.
Ограничений на тип связи в этом случае не накладывается.
При форварде происходит "размывание" целостности интерфейса по нескольким
объектам. Пусть интерфейс I1 содержит методы i11 и i12, Б переадресует I1
объекту А, но реализует i12. Вызвать из А.i11 метод i12 так что бы получилось
тоже, что при вызове Б.i12 мы не можем поскольку this Б недоступен.
По этому мы не можем считать форвард полноценной реализацией интерфейса.
В остальные двух вариантах this сохраняется и этот дефекта нет.
Рассмотрим, в каком виде может быть передан this при делегации.
Простой и неверный вариант - как единичный параметр функции.
Интерфейс I1 может быть переадресован из объекта В в объект Б, а уже из него в
А. Это однако не мешает передавать this как единственный параметр, достаточно
передать В.
Мешает вот что: допустим объект А требует что бы в объекте Б кроме интерфейса
I1 был еще и интерфейс I2. Однако объекту С ничто не мешает его не иметь
(вспомним что мы связи ограничены только наличием интерфейсов, а Б их имеет).
Следовательно передаваемый this должен состоять из this и Б и В и ... При один
конец списка есть объект первоначального вызова, а второй ближайший по связи
объект.
Размер этого списка тоже не фиксирован, поскольку порядок вызова может быть как
В-Б-А так и Г-Д-Б-А в одном и том же объекте.
Есть еще одна проблема если B и Б имеют I2, но не переадресуют его друг другу,
то Б.m2 и В.m2 вызов разных методов. Однако эта неоднозначность кажущаяся.
Описание связи А-Б задает, что используется объект Б, а на то как он реализован
в А наплевать. Дело в том, что методы одного интерфейса в одном объекте должны
быть однозначны и доступны (в чем состоит проблема форварда), хотя мы и гоняем
этот вызов как вшивого по бане, а в этом случае от интерфейса в разных объектах
однозначности и не требуется.
Вызов методов делегированного интерфейса минуя делегирующий объект невозможен,
поскольку при этом нет this.
При реализации методов делегированного интерфейса должна быть указана связь,
что дает тип делегированного this и, при одинаковых его типах, позволяет
различить связи.
Теперь попробуем понять, что происходит, когда часть делегированного интерфейса
реализована не в конечном объекте. Из самой идеи делегации следует, что
вызывать надо последнюю реализацию в цепочке делегации, причем с неизменными
параметрами и объектом.
Перекрытие "посередине" возможно только в виде пред- и пост-методов (хуков),
между пред- и пост-методами возможна передача переменных. Однако они не могут
менять ни параметры вызова ни интерфейсы участвующие в цепочке делегации, в том
числе косвенно. Отследить это автоматически имхо не возможно. Остается либо
исключить эту возможность либо надеяться на прямые руки.
Вызов предыдущей реализации в цепочке делегации возможен только вручную,
поскольку он может требоваться и до и после и посередине реализации и не
требоваться вовсе.
Тут даже есть несколько вариантов:
1. Hе возможен.
2. Возможен. Предыдущая реализация есть.
3. Возможен. Предыдущая реализация возможно есть.
4. Обязателен. Очевидно, предыдущая реализация должна быть.
5. Обязателен, если есть предыдущая реализация.
Я не уверен что это можно проверить при компиляции, но очень хочется.
Делегируемый интерфейс должен быть реализован полностью, т.е. все его методы
должны иметь реализацию, однако в общем случае это трудно проверить.
Если связь В-Б однонаправленная, то в цепочке делегации В-Б-А при замене А
необходим глобальный просмотр всех объектов.
Если связь В-Б двунаправленная, то всех объектов В связанных с Б.
Как видно из выше изложенного при явном объявлении интерфейсов делегации не
возникает неоднозначностей. Однако если нам нужно добавить интерфейс, который
не был явно описан (динамическое приведение типов), и объект делегирует по
более чем одной связи то не известно из какой связи брать искомый интерфейс.
Если язык имеет только единичное наследование, то эта неоднозначность может
быть частично разрешена для дочерних к делегированным интерфейсов.
Замечание о производительности:
Хочется отметить, что дополнительной памяти это не требует. Делегированный this
уже содержится на стеке вызова. А вот накладные расходы на вызов интерфейсов,
кроме первоначального объекта, значительно больше, чем при VMT. В худшем случае
требуется просмотр всего списка.
Агрегированные вызовы не вкладываются, в том смысле, что не требуют передачи
ссылок от вызова к вызову. Обычный метод вызывается как обычно. А вызовы
агрегированных методов, после того как найден нужный объект, начинают новый
агрегированный вызов с него.
Dmitry