Московский физико-технический институт
Опубликован: 23.12.2005 | Доступ: свободный | Студентов: 2867 / 253 | Оценка: 4.61 / 4.44 | Длительность: 27:18:00
ISBN: 978-5-9556-0051-2
Лекция 8:

Эмулируем множественное наследование

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >
Аннотация: Обзор возможностей ActionScript 1.0 по эмуляции различных языковых средств. Основной принцип эмуляции множественного наследования при помощи прототипного. Сложности эмуляции и их преодоление. Реализация виртуальных базовых классов. Работа с системными базовыми классами. Встраивание в иерархию полученного класса-наследника. Полезные утилиты.

Основания для оптимизма

Отсутствие множественного наследования - это одна из тех вещей, которая сильно раздражает программистов, переходящих с С++ на Java. (Есть еще довольно объемистый список таких вещей. Например, отсутствие перегрузки операторов и типизованных коллекций. А также отсутствие параметров по умолчанию в функциях и т.д.) К счастью, множество недостатков ActionScript первой версии слегка компенсируется тем, что какие-то вещи можно в той или иной степени эмулировать. Например, параметры по умолчанию эмулировать совсем просто:

function defArgExample(_text, _prefix){
  if (_prefix != undefined) trace(_prefix + ": " + _text);
  else trace(_text);
  return;
}
defArgExample("building 8");
defArgExample("building 8", "address");

Этот код выводит

building 8
address: building 8

Или можно сделать даже так:

function defArgExample1(_text){
  var tmpString;
  if (arguments.length > 1){

	for (var i=arguments.length; -i>=1;) tmpString += arguments[i] + ": ";
	  trace(tmpString + _text);
  }
  else trace(_text);
  
  return;
}

defArgExample1("building 9");
defArgExample1("building 9", "address");
defArgExample1("building 9", "address", "official data");

в результате чего получим:

building 9
address: building 9
official data: address: building 9

Вот и все - параметры по умолчанию готовы. Ранее вы могли видеть, как были эмулированы статические и приватные поля и методы. Так что возможности для эмуляции "всего на свете" интерпретатор ActionScript предоставляет весьма богатые. А это, в свою очередь, дает нам некоторые основания для оптимизма и в задаче об эмуляции множественного наследования.

Переходим к делу

Сразу отметим: авторы осознают, что проекты такого размера, которые требуют полноценного множественного наследования, на Флэш МХ вряд ли будут делаться. Хотя: как знать? Так или иначе, данная лекция является, скорее, демонстрацией возможностей языка ActionScript (а также и ECMA-Script, который, напоминаем, в виде JavaScript используется в браузерах). И хотя полноценное множественное наследование вам, скорее всего, не понадобится, возможно некоторые приемы, которыми мы сейчас воспользуемся, когда-нибудь используете и вы.

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

for (var name in sourceObj){
	destObj[name] = sourceObj[name];
}

При этом надо взять в качестве sourceObj не что иное, как sourceClass.prototype; аналогично, в качестве destObj берем destClass.prototype. Более того, видимо, именно так и следует поступить, если все, что вам нужно от одного из базовых классов - это его поля и методы. Совсем другая ситуация возникает, если серьезная инициализационная работа производится в конструкторах, которые в свой черед вызывают базовые конструкторы и т.д. В таком случае цепочку __proto__ "сворачивать", копируя все методы, нельзя. Но возникает другая идея: а нельзя ли скопировать эту цепочку целиком? Скопировать каждую из цепочек (для каждого из базовых классов), а затем составить эти "поезда" друг за другом!

Идея хорошая, но давайте посмотрим, какие трудности нам придется преодолеть в процессе ее реализации.

Во-первых, надо ли копировать цепочки? Нельзя ли просто установить ссылку __proto__ самого последнего из базовых классов (для определенности будем дальше говорить "самого верхнего класса") одной из цепочек так, чтобы она указывала на самый нижний класс в другой цепочке? К сожалению, это невозможно, ибо приведет к "порче" ряда классов из первой цепочки - у них вдруг появятся базовые, которых отродясь не бывало. А ведь эти классы запросто могут использоваться безо всякого отношения к нашему множественному наследованию. Конечно, лишние базовые классы - это не то же самое, что недостающие; поведение изменится лишь в небольшом числе специальных случаев; но все равно неприятно. Так что копировать нужно.

Во-вторых, самый первый из конструкторов каждой субцепочки (так мы будем называть всю цепочку одного из базовых классов нашего "множественного наследника") надо будет вызывать по отдельности. И специальным образом формировать для каждого из таких конструкторов массив аргументов. Впрочем, здесь нет ничего необычного, ведь примерно то же самое происходит при вызове базовых конструкторов в С++.

В-третьих, как только мы начинаем работать с множественным наследованием, немедленно возникает проблема дублирования базовых классов. Что делать, если один и тот же класс повторяется в двух различных субцепочках? Один из естественных ответов на этот вопрос - не делать ничего. Поскольку у нас в субцепочках будет все-таки не сам класс, а его копии, такая ситуация ничем особенным не грозит. Однако есть два неудобства: а) занимается лишнее место в памяти и б) конструкторы этого класса в разных субцепочках могут делать вещи, которые конфликтуют друг с другом. В С++ обе эти проблемы решаются введением виртуальных базовых классов. Нам нужно будет предусмотреть механизм, который позволит иметь вместо двух совпадающих классов в разных субцепочках один класс, причем его конструктор будет вызываться прямо из конструктора "множественного наследника", как это делается для виртуальных базовых классов в С++.

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

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >