Хотелось бы узнать кто что думает по поводу замены механизма наследования интерфейсов на механизм включения. Что такое наследование интерфейсов, думаю, всем понятно. Например, если
то, интерфейс IB является потомком от IA и, стало быть, имеет все процедуры объявленные в IA. Переменную типа IB можно использовать вместо переменной типа IA. Другими словами, переменная типа IA совместима по присваиванию с переменной типа IB (обратное не верно). Теперь рассмотрим случай когда IA и IB не связаны друг с другом наследованием:
Хотя IA и IB не связаны друг с другом наследованием, но интерфейс IB включает в себя все процедуры объявленные в интерфейсе IA. Теоретически, любая переменная типа IB потенциально может быть использована вместо переменной типа IA (компилятор, правда, с этим будет не согласен, но это его проблемы). Будем говорить, что один (включающий) интерфейс включает в себя другой (включаемый) интерфейс, если он включает в себя все процедуры объявленные во включаемом интерфейсе. Сразу же ответим на вопрос сколько более мелких интерфейсов включает в себя один большой интерфейс имеющий N-штук процедур. Очевидно, количество включамых интерфейсов равно:
Количество включаемых интерфейсов экспоненциально растет (2^N-2) по мере увеличения количества (N) процедур в интерфейсе. Интерфейс состоящий из N=10 процедур, включает в себя 2^10 — 2 = 1022 более мелких интерфейсов. Интерпретация этого явления очень проста. Вот, например, сколькими интерфейсами обладает калькулятор? Ни в жизнь не сосчитаете. Ведь калькулятором можно еще и гвозди забивать! Когда программист проектирует класс объекта, он определяет какие интерфейсы объекты этого класса будут поддерживать
TMyCalculator = CLASS (TInterfacedObject, ICalculate, IStorable, IPaintable, ...)
//...END;
Все интерфейсы поддерживаемые объектами этого класса жестко фиксируются в момент написания класса. Но дело в том, что если интерфейс содержит хотябы с десяток процедур, то разработчику будет не под силу указать даже малую часть всех тех 2^10 — 2 = 1022 возможных вариантов использования объектов этого класса. Программист просто утомиться их перечислять, не говоря о том чтобы еще и каждому из этих подинтерфейсов дать свое уникальное имя (и GUID...). Всвязи с этим, возникает вопрос: А не стоит ли для интерфейсов ввести такое понятие как ВКЛЮЧЕНИЕ ИНТЕРФЕЙСОВ и, соответственно, отказаться от наследования интерфейсов, поскольку включение — есть более мощный механизм нежели механизм наследования?
Практически, это будет выражено в том, что когда разработчик будет писать код класса, то ему не надо будет явно перечислять все интерфейсы поддерживаемые объектами этого класса. Вместо этого, когда у объекта этого класса будет запрошена услуга по какому-либо интерфейсу, то среда исполнения сама создаст соответствующую этому интерфейсу интерфейсную переменную ссылающуюся на соответствующие процедуры этого объекта (если это возможно; если невозможно, значит не создаст). Таким образом, механизм включения интерфейсов позволит писать меньше кода и создавать более гибкие программы.
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>"Включение" vs "Наследование" интерфейсов
Разрешите сперва скорректировать термины.
"Включение" — агрегация (мне так удобнее).
"Наследование" — статическое наследование (именно о нём вы говорите в своём посте).
Наследование — механизма разделения ресурсов между объектами связанными отношением потомок-предок.
Итак. Агрегация и статическое наследование могут выступать в качестве различных реализаций механизма наследования в ООП, как способа разделения ресурсов между объектами связанными отношением потомок-предок. На этом сходство исчерпывается и идут отличия.
Статическое наследование по-сути есть статическое копирование реализации предка в реализацию клиента, выполняемое на этапе компиляции. На базе языковой поддержки оно присуще языкам с классической реализацией объектно-ориентированного подхода. Другой отличительной чертой этих языков (как правило), тесно связанной со статическим наследование, является статическая типизация (исключение составляет, разве что, Smalltalk).
Агрегация + делегирование — это более продвинутая реализация механизма разделения ресурсов между предком и потомком. На уровне языковой поддержки она реализована, например, в языке Self.
Агрегация и делегирования — механизмы периоды исполнения (динамический подход). Статическое наследование — механизм периода компиляции (статический подход).
Очень часто статического наследования для реализации полноценной объектно-ориентированной архитектуры в таких языках как C++, Pascal не хватает, поэтому на программном уровне оно дополняется агрегацией с делегированием (добрая половина паттернов банды четырёх решает именно такие задачи). Но смешивать эти подходы на языковом уровне не имеет смысла. Почему — чистая философия без какой либо конкретики, основанная на том принципе, что статическая типизация призвана облегчить процесс программирования и поиска ошибок, добавление агрегации — механизма, динамического по сути, может извести на нет все благие начинания.
Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.
Здравствуйте, Mr. None, Вы писали:
MN>Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>>"Включение" vs "Наследование" интерфейсов
MN>Разрешите сперва скорректировать термины. MN>"Включение" — агрегация (мне так удобнее). MN>"Наследование" — статическое наследование (именно о нём вы говорите в своём посте). MN>Наследование — механизма разделения ресурсов между объектами связанными отношением потомок-предок.
Боюсь, что Вы это о чем-то о своем...
Множество {1,2,3,4} включает в себя подмножество {2,3}, но оно не агрегирует его. Агрегация — это когда автомобиль агрегирует в себе свой двигатель, сиденья, бензобак и колеса.
Интерфейс — это декларация объединенных в один список объявлений процедур (только объявлений, без реализации). Один интерфейс включает в себя другой интерфейс — это когда один (маленький) список полностью входит в другой (больший) список декларированных процедур.
Агрегация на языке интерфейсов выражается совсем по другому, вот так
Ia = INTERFACE
PROCEDURE f();
PROCEDURE g();
END;
Ib = INTERFACE
FUNCTION GetA(): Ia; // вот она - агрегация какаяPROCEDURE h();
PROCEDURE k();
END;
VAR
b: Ib;
BEGIN//...
b.GetA.f();
Если я правильно понял, что вы имеете ввиду, то именно такой способ работы с объектами принят в OCaml. Там в функцию можно передать любой класс, в котором есть нужные методы с фиксированными типами параметров и именами. Т.е. в
f x = x.method 1 "1"
Можно передать любой объект с методом method типа int, string -> во что-то
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>"Включение" vs "Наследование" интерфейсов
По этому пути уже пошла OMG в стандарте UML 2.0. Интерфейсы функционируют так, как вы описали и при этом еще поддерживается их наследование. Так что одно другому не противоречит.
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>Практически, это будет выражено в том, что когда разработчик будет писать код класса, то ему не надо будет явно перечислять все интерфейсы поддерживаемые объектами этого класса.
Идея интересная, но к сожалению, не совсем согласен.
SYG>Вместо этого, когда у объекта этого класса будет запрошена услуга по какому-либо интерфейсу, то среда исполнения сама создаст соответствующую этому интерфейсу интерфейсную переменную ссылающуюся на соответствующие процедуры этого объекта (если это возможно; если невозможно, значит не создаст). Таким образом, механизм включения интерфейсов позволит писать меньше кода и создавать более гибкие программы.
Есть один очень важный момент, про который часто забывают. Если у нас объект унаследован от нескольких интерфейсов, то просто поглядев на список наследования уже можно понять, где этот объект может быть применён и отчасти — чем он является.
Если спецификация интерфейса должна содержать перечисление всех его методов, то это будет попросту неудобно — люди будут стремиться заменить список одним-двумя словами, отражающими сущность явления. Т.е., через некотрое время придём к тому, от чего ушли — интерфейс есть цельная неделимая сущность.
А что до адаптирования, это относительно несложно решить шаблонами (если речь — о C++) или runtime-связыванием.
PS. Говорил же, меньше читайте адептов ООП.
... << RSDN@Home 1.1.3 stable >>
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>Боюсь, что Вы это о чем-то о своем...
SYG>Множество {1,2,3,4} включает в себя подмножество {2,3}, но оно не агрегирует его. Агрегация — это когда автомобиль агрегирует в себе свой двигатель, сиденья, бензобак и колеса.
Читал и смеялся. Давайте обозначим 1=двигатель, 2=сиденья, 3=мотор... Дальше продолжать? . А вообще по изначальной теме топика — а какому это маньяку понадобятся ВСЕ возможные комбинации методов? Если уж ему нужно 1023 интерфейса, то пусть с ними возится. ИМХО так. Давайте приводить примеры из жизни. То, что послужило началом топика никакого отношения к реальности не имеет. Учим матчасть и проектирование.
P.S. Ничего личного, просто вопрос был дурацкий. ТАК никто не делает...
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>"Включение" vs "Наследование" интерфейсов SYG>...
В С++ эта идея уже реализована и давно используется. Техника ее применения называется "обобщенное программирование". Вводится понятие "концепции", которое можно трактовать, как некоторый запрашиваемый интерфейс, который не объявляется в классах явно. Или другими словами: концепция — это множество классов, удовлетворяющих некоторым требованиям. Таким образом, новая концепция появляется тогда, когда появляются новый набор требований, и при этом некоторые старые классы могут автоматически подпадать под новую концепцию. Также вводится понятие обобщенного алгоритма, основное отличие которого от обычного заключается в том, что обычный алгоритм работает на уровне классов, а обобщенный — на уровне концепций.
Между концепциями может возникать отношение "развития", когда одна концепция развивает другую. Например, концепция "итератор произвольного доступа" развивает концепцию "однонаправленный итератор". Таким образом, получаются иерархии концепций, наподобии иерархий классов. Но важно то, что между наследованием классов и развитием концепций не ставится никакого "versus". Одно другого дополняет.
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Есть один очень важный момент, про который часто забывают. Если у нас объект унаследован от нескольких интерфейсов, то просто поглядев на список наследования уже можно понять, где этот объект может быть применён и отчасти — чем он является.
1) Объект не наследует интерфейсы, а реализовывает их.
2) А чем являются и каков способ применения следующих объектов: "палка из дерева", "кусок камня", "комок ваты", "капля чернил", "калькулятор, довольно прочный на вид, так что им можно гвозди забивать" и т.д? Я же привел формулу g(N) = 2^N — 2 для количества разных вариантов использования одного и того же объекта, из которой видно, что это самое количество вариантов растет экспоненциально. Следовательно, если разработчик класса объекта будет явно прописывать возможные варианты использования этих объектов, то он сможет написать лишь ничтожно малую часть вариантов (устанет писать).
Здравствуйте, Larm, Вы писали:
L>Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>>Боюсь, что Вы это о чем-то о своем...
SYG>>Множество {1,2,3,4} включает в себя подмножество {2,3}, но оно не агрегирует его. Агрегация — это когда автомобиль агрегирует в себе свой двигатель, сиденья, бензобак и колеса.
L>Читал и смеялся.
Кстати, Вы в курсе что теория множеств становится логически противоречивой когда начинают рассматривать множества множеств? То есть множество агрегирующее (содержащее) в себе другие множества — математически не совсем корректный объект. Поэтому, множество {1,2,3,4} именно включает в себя подмножества {1,2}, {2,3}, {3,4,5}, {4}, {1,4}, но оно не агрегирует их.
Здравствуйте, prVovik, Вы писали:
V>В С++ эта идея уже реализована и давно используется.
Но Си++-совые templates нельзя скомпилировать лишь однажды и распространять в виде бинарных модулей. Как ни крути, но работают они, грубо говоря, только на уровне исходного текста программы, как мега-супер-продвинутый-макрос. Идея использования интерфейсов как раз и заключается в том. что модуль с классом объекта создается лишь однажды (сторонним производителем), а все остальные, зная интерфейс тех объектов, используют этот бинарный модуль. А я в этой ветке форума поставил вопрос, почему нельзя делать так:
I1 = INTERFACE
PROCEDURE f();
PROCEDURE g();
END;
I2 = INTERFACE
PROCEDURE f();
PROCEDURE g();
PROCEDURE h();
PROCEDURE k();
END;
VAR a: I1;
b: I2;
//...
a := b; // хочу чтобы это было разрешено! Так как "I2" включает в себя все методы "I1"
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>Кстати, Вы в курсе что теория множеств становится логически противоречивой когда начинают рассматривать множества множеств? То есть множество агрегирующее (содержащее) в себе другие множества — математически не совсем корректный объект.
А можно пару комментариев, почему? Это очень широко применяемый объект, и мне хотелось бы знать, в чем его некорректность. SYG>Поэтому, множество {1,2,3,4} именно включает в себя подмножества {1,2}, {2,3}, {3,4,5}, {4}, {1,4}, но оно не агрегирует их.
Почему "поэтому"? Отношения принадлежности работают между множествами и элементами, отношения включения — между множествами. Никакого противоречия здесь нет. Как, впрочем, и термина "агрегировать" по отношению к множествам.
А вот "множество всех подмножеств множества {1, 2, 3, 4}" содержит перечисленные тобой множества как элементы.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>2) А чем являются и каков способ применения следующих объектов: "палка из дерева", "кусок камня", "комок ваты", "капля чернил", "калькулятор, довольно прочный на вид, так что им можно гвозди забивать" и т.д? Я же привел формулу g(N) = 2^N — 2 для количества разных вариантов использования одного и того же объекта, из которой видно, что это самое количество вариантов растет экспоненциально.
Формулу-то ты привел. Но таких формул можно описать бесчисленное множество. И они никак не влияют на практику программирования. SYG>Следовательно, если разработчик класса объекта будет явно прописывать возможные варианты использования этих объектов, то он сможет написать лишь ничтожно малую часть вариантов (устанет писать).
Вот именно. Но я могу придумать еще миллион способов утомить разработчика. И все будут столь же бессмысленными. Пока что я не вижу никакой причины бедному разработчику заниматься этой ерундой. Пока что я вижу, что ты придумал задачу, которая никогда не стоит на практике, и предлагаешь способ для ее решения.
Имхо, в программировании вполне достаточно настоящих проблем, чтобы решать еще и надуманные.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, S.Yu.Gubanov, Вы писали: SYG>Но Си++-совые templates нельзя скомпилировать лишь однажды и распространять в виде бинарных модулей.
Можно. Ограниченно, но можно. Впрочем, к теме это отношения не имеет. SYG>Как ни крути, но работают они, грубо говоря, только на уровне исходного текста программы, как мега-супер-продвинутый-макрос. SYG>Идея использования интерфейсов как раз и заключается в том. что модуль с классом объекта создается лишь однажды (сторонним производителем), а все остальные, зная интерфейс тех объектов, используют этот бинарный модуль. А я в этой ветке форума поставил вопрос, почему нельзя делать так: SYG>
SYG>I1 = INTERFACE
SYG> PROCEDURE f();
SYG> PROCEDURE g();
SYG>END;
SYG>I2 = INTERFACE
SYG> PROCEDURE f();
SYG> PROCEDURE g();
SYG> PROCEDURE h();
SYG> PROCEDURE k();
SYG>END;
SYG>VAR a: I1;
SYG> b: I2;
SYG>//...
SYG> a := b; // хочу чтобы это было разрешено! Так как "I2" включает в себя все методы "I1"
SYG>
А чем это хуже I2 = interface(I1) ? Или ты полагаешь, что есть какой-то шанс на случайное совпадение сигнатур I1 и I2, которое позволит делать приведение типов осмысленным?
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, S.Yu.Gubanov, Вы писали:
SYG>Здравствуйте, prVovik, Вы писали:
V>>В С++ эта идея уже реализована и давно используется.
SYG>
SYG>I1 = INTERFACE
SYG> PROCEDURE f();
SYG> PROCEDURE g();
SYG>END;
SYG>I2 = INTERFACE
SYG> PROCEDURE f();
SYG> PROCEDURE g();
SYG> PROCEDURE h();
SYG> PROCEDURE k();
SYG>END;
SYG>VAR a: I1;
SYG> b: I2;
SYG>//...
SYG> a := b; // хочу чтобы это было разрешено! Так как "I2" включает в себя все методы "I1"
SYG>
Ну если руководствоваться вашим "хочу", то возможны например такие конструкции:
/* Пример напиан по мативам книги "Как размножаются ёжики". За основу взято предложение: За окном шёл снег и рота краснармейцев. ;-) */class Рота_Красноармейцев
{
public:
void Идёт();
};
class Снег
{
public:
void Идёт();
};
Снег obj1;
Рота_Красноармейцев obj2 = obj1; // Постойте, "рота красноармейцев" не то же самое что и "снег"!
obj2.Идёт(); // Да и ходят они по разному!!
Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.
Здравствуйте, Sinclair, Вы писали:
S>Имхо, в программировании вполне достаточно настоящих проблем, чтобы решать еще и надуманные.
Эта задача не надуманная. Вот пример:
Есть два объекта. Первый объект использует второй. Первый объект не знает конкретного типа второго объекта, а взаимодействует с ним через определенный интерфейс. Этот механизм имеет следующую реализацию:
1) Сначала пишется модуль с интерфейсом (D)
2) Потом пишутся (не важно в каком порядке) два модуля, соответственно, с классом первого (A) и классом второго (B) объектов.
3) Во время работы программы первому объекту дается второй объект и все работает, т.к. в момент написания классов этих объектов они оба знали о заблаговременно определенном интерфейсе D.
D
(A)----<----(B)
Эта система является гибкой, в том смысле, что вместо второго объекта можно использовать какой угодно другой лишь бы он тоже реализовывал заданный интерфейс D. Все кажется просто и лучше быть не может. Теперь рассмотрим систему покрупнее. Пусть теперь есть много разных объектов использующих друг друга.
Эта система задается графом, дуги которого есть интерфейсы, а вершины этого графа есть какие угодно объекты реализовывающие интерфейсы соответствующие входящим и исходящим дугам из этих вершин. Зададимся вопросом, на сколько такая система гибка? Да, мы по прежнему можем вместо каждого конкретного класса (X) объектов использовать какой-то другой класс, лишь бы только интерфейсы подходили. Но видители в чем дело. Когда мы, два-три года назад, писали классы объектов, мы и не думали что сегодня захотим объединить их именно в такую схему как показано выше, мы объединяли их немного в другую систему чуточку с другой топологией и чуточку с другими интерфейсами D1', D2', D3',... Чтобы сейчас объединить те уже написанные классы объектов в новую систему взаимодействия нам надо, пусть не много, но изменить исходный код каждого класса, как минимум добавив в заголовке каждого класса, что объекты этих классов теперь могут быть использованы еще и по новым интерфейсам D1, D2, D3, ... И так будет каждый раз когда мы захотим использовать старые уже написанные классы объектов для построения других систем. Почему нам пришлось вносить изменения в исходный код классов? Да потому что есть такое дурацкое требование в определении каждого класса явно прописывать все способы использования объектов этого класса. Какова альтернатива? Альтернативой является системы собираемые также как в детском конструкторе. Есть элементарные компоненты, а уже их сочетание может дать массу всяких систем без внесения изменений в сами компоненты
Здравствуйте, S.Yu.Gubanov, Вы писали: SYG>Тем, что в случае N=4, g(4) = 2^4 — 2 = 14, будешь наследоваться от 14 интерфейсов? Кстати, у интерфейсов множественного наследования нету.
Это в Delphi нету. Используй полноценный ООЯП и наследуйся от 4х интерфейсов S>>Или ты полагаешь, что есть какой-то шанс на случайное совпадение сигнатур I1 и I2, которое позволит делать приведение типов осмысленным? SYG>А что делать? Предложи что-то более безопасное, но тем не менее, более гибкое чем есть?
public interface IList : ICollection, IEnumerable
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, S.Yu.Gubanov, Вы писали: SYG>Эта задача не надуманная. Вот пример:
SYG>Есть два объекта. Первый объект использует второй. Первый объект не знает конкретного типа второго объекта, а взаимодействует с ним через определенный интерфейс. SYG>Эта система является гибкой, в том смысле, что вместо второго объекта можно использовать какой угодно другой лишь бы он тоже реализовывал заданный интерфейс D. Все кажется просто и лучше быть не может.
Именно. SYG>Теперь рассмотрим систему покрупнее. Пусть теперь есть много разных объектов использующих друг друга. SYG>
SYG>Эта система задается графом, дуги которого есть интерфейсы, а вершины этого графа есть какие угодно объекты реализовывающие интерфейсы соответствующие входящим и исходящим дугам из этих вершин. Зададимся вопросом, на сколько такая система гибка? Да, мы по прежнему можем вместо каждого конкретного класса (X) объектов использовать какой-то другой класс, лишь бы только интерфейсы подходили. Но видители в чем дело. Когда мы, два-три года назад, писали классы объектов, мы и не думали что сегодня захотим объединить их именно в такую схему как показано выше, мы объединяли их немного в другую систему чуточку с другой топологией и чуточку с другими интерфейсами D1', D2', D3',...
Очень интересно. Вот у тебя есть два класса, связанные по интерфейсу D3. Так? Зашибись. Это означает, что клиенту нужна совершенно определенная функциональность от сервера этого интерфейса. И сервер, соответственно обязан эту функциональность предоставить. И, естественно, об этом рассказать при помощи включения в список реализованных интерфейсов D3. SYG>Чтобы сейчас объединить те уже написанные классы объектов в новую систему взаимодействия нам надо, пусть не много, но изменить исходный код каждого класса,
Ну естественно. Потому, что объединение — это процесс двусторонний. SYG>Почему нам пришлось вносить изменения в исходный код классов? Да потому что есть такое дурацкое требование в определении каждого класса явно прописывать все способы использования объектов этого класса.
Нет! Потому, что теперь клиенты требуют от серверов другой функциональности. Ты что, всерьез полагаешь, что эта функциональность совершенно случайно оказалась уже реализованной в старых классах? Не смеши мои тапочки. SYG>Какова альтернатива? Альтернативой является системы собираемые также как в детском конструкторе. Есть элементарные компоненты, а уже их сочетание может дать массу всяких систем без внесения изменений в сами компоненты
Если ты мне предложишь хоть один пример такой системы, который никак не выражается стандартным подходом, я задумаюсь над реальностью задачи. Вот только не надо иксов и дэ с номерами. Расскажи, как можно собрать объекты новым, не предусмотренным изначально способом, без их переделки.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.