Часть 2

Персональный блог одноименного форумчанина. Человека и парохода, не побоюсь этого сравнения :)

Модератор: tyomitch

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Часть 2

Сообщение tyomitch » 22.08.2007 (Ср) 19:59

(часть 1)


* 4. Расширяемые классы

Теперь предположим, что нам захотелось добавить в класс новый метод в рантайме. (Понятно, что этот метод будет доступен только через позднее связывание.) Когда у нас есть анонимные методы, писать такой код становится гораздо удобнее. Пусть, например, в классе AnotherClass есть свойство Public Property Get AllMyObjects() As MyObjects, но нет метода ValidateObjects; а нам очень хочется, чтобы этот метод в нём был. И пусть у нас ещё есть волшебное ключевое слово Extend, которое принимает объект, имя метода, и функтор произвольного типа. (Оно могло бы ещё принимать VbCallType.) Тогда мы смогли бы написать:
Код: Выделить всё
Dim c As AnotherClass
Set c = WhateverInstance
Extend c, "ValidateObjects", Delegate ValidateObjects(ByVal My AnotherClass, ByVal Parameters As Variant) As Boolean
    ValidateObjects = True
    My.AllMyObjects.EnumMyObjects AddressOf Delegate ValidateObject(ByVal obj As MyObject) As Boolean
        ValidateObjects = obj.Validate(Parameters)
        ValidateObject = ValidateObjects
    End Delegate
End Delegate
Debug.Assert CObj(c).ValidateObjects("Pack my box with five dozen liquor jugs")



Как такое волшебное слово работало бы? Это понятно – оно бы агрегировало к расширяемому объекту переданный функтор, и перехватывало бы обработчик IDispatch так, чтобы вызовы метода с новым именем перенаправлялись бы функтору. Такой функтор жил бы до самой смерти того объекта, который им расширен. Но получается, что код такого функтора оказывается методом сразу двух объектов: того, где функтор создан (известного компилятору типа), и того, который им расширен (неизвестного компилятору типа). “Альтер-эго” функтора передаётся ему перехватчиком IDispatch в качестве явного первого параметра, который непременно должен иметь объектный тип, и непременно (для предотвращения программистских ошибок) называться My. Одним и тем же функтором можно расширять объекты разных типов; тогда его параметр My должен иметь тип As Object. Функтор без параметра My просто не получит ссылку на тот объект, как метод которого он вызван.


* 5. Конструкторы классов

Что делать, если хочется добавить новый метод не просто к одному объекту AnotherClass, а ко всему классу – чтобы все создаваемые объекты этого класса были расширены нашим новым методом? Очевидный ответ – создавать их не оператором New, а нашим собственным конструктором, который после создания каждого экземпляра вызовет для него Extend.

Теперь – внимание, фокус! Зачем нам вообще оператор New? Достаточно способа создать объект с нужным набором полей (этим объектом у нас будет блок переменных замыкания), а навесить на такой объект методы мы сможем и сами:
Код: Выделить всё
Public Function CreateAnotherClass() As Object
Dim AllMyObjects As MyObjects
    Set CreateAnotherClass = New Object
    Extend CreateAnotherClass, "AllMyObjects", Delegate() As MyObjects
        Set Delegate = AllMyObjects
    End Delegate, vbGet
    Extend CreateAnotherClass, "AllMyObjects", Delegate(ByVal RHS As MyObjects)
        Set AllMyObjects = RHS
    End Delegate, vbSet
    Extend CreateAnotherClass, "ValidateObjects", Delegate ValidateObjects(ByVal Parameters As Variant) As Boolean
        ValidateObjects = True
        AllMyObjects.EnumMyObjects AddressOf Delegate ValidateObject(ByVal obj As MyObject) As Boolean
            ValidateObjects = obj.Validate(Parameters)
            ValidateObject = ValidateObjects
        End Delegate
    End Delegate
End Function


Раннее связывание с методами при этом, естественно, пропадает из языка. Чтобы вернуть его хоть в какой-то мере, воспользуемся интерфейсами. Пусть Extend умеет вместо строкового имени метода принимать его имя вместе с именем интерфейса: Extend CreateAnotherClass, IAnotherClass.ValidateObjects, Delegate ValidateObjects… Реализация усовершенствованного таким образом Extend практически не меняется: просто перехватываться будет не IDispatch, а IAnotherClass. Но возникает проблема: что делать с интерфейсами из нескольких методов, только часть которых была реализована вызовами Extend? Можно, например, генерировать при вызове нереализованных методов ошибку E_NOTIMPL; наверное, ничего более полезного сделать всё равно не удалось бы.

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


* 6. Что получилось?

В том, что получившийся язык без классов является в полной мере ОО-языком, наверное, не будет сомнений. Инкапсуляции можно достичь даже на более тонком уровне, чем в традиционных ОО-языках, объявляя общие данные произвольного набора методов как локальные переменные содержащей их процедуры. Наследника можно создать вообще для любого отдельного объекта, добавив в него новые методы, которые могут “втянуть за собой” в объект новые поля из блоков переменных своих замыканий. Если собрать каскад из нескольких конструкторов, то получится конструктор объектов-наследников. Ну, и полиморфизм тоже представлен в полной мере: у каждого объекта свой динамически формируемый набор интерфейсов, и каждому интерфейсу каждого объекта соответствует отдельная виртуальная таблица. Совпадающие виртуальные таблицы пусть для экономии памяти сливает рантайм; только экономия памяти, похоже, выходит из моды, так что можно и не заморачиваться :-)

В нашем языке у методов нет явного указания их области видимости. “Просто” добавленные к объекту методы будут публичными. Если метод ссылается на поля объекта, но не добавлен в него, то это приватный метод – прочие методы могут вызывать его только через ссылку на его функтор. Дополнительным бонусом оказывается то, что ненужные приватные методы сами отвалятся от объекта, как только на них пропадут ссылки из других методов. То же самое с полями объекта: как только на блок полей исчезнут ссылки, этот блок самоуничтожится, не затрагивая остальные части объекта. Получается, что объект может не только постепенно, по мере необходимости, создаваться, но и постепенно разрушаться. Далее, Friend-методы определяются точно так же, как и в VB: это методы, реализующие неэкспортируемые интерфейсы. А вот Protected-методов нет: во-первых, их нет и в VB, так что мы ничего не потеряли. Во-вторых, они бесполезны как средство сокрытия “ненужных” деталей реализации: всегда можно создать потомка, вытаскивающего защищённые методы наружу (этот трюк метко прозвали “Паблик Морозов”).

Так что в результате мы придумали полноценно объектно-ориентированный и полностью COM-совместимый язык, с поддержкой раннего и позднего связывания в произвольно выбираемом программистом сочетании. И всё это – без классов! Фактически, вся программа оказывается состоящей из чистого кода и описаний интерфейсов, и тем самым ясно и недвусмысленно поделенной на объявления и реализацию. Нехило, да?


* 0. Некомпьютерный бонус
Пишут, что есть вид креветок под названием "prawn" (читается как "pr0n"). Может, это способно объяснить увлечение ими падонкавских толп?
Изображение

Вернуться в Tyomitch

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 15

    TopList  
cron