Здравствуйте, iZEN, Вы писали:
ZEN>Здравствуйте, Kluev, Вы писали:
ZEN>У меня похожий образец UI в Java, но я обошёлся агрегированием вместо множественного наследования. ZEN>Есть view — канва, на которой отображаются виджеты. ZEN>Есть proxy_doc — синглетон-контейнер для однотипнах объектов-моделей — doc'ов в вашей терминологии. ZEN>proxy_doc ничего не известно о конкретной моделе, но известно об области канвы; он может только правильно отрисовать модель, нуждающуюся в отрисовке (реконструировать dirty_doc, заодно "превратив" их обратно в doc ).
ZEN>Основной момент в таком образце: резко сокращаются накладные расходы на перерисовку всей сцены. Затрагивается только то, что действительно нуждается в обновлении. "Лишние" требования моделей на перерисовку себя отсекаются в proxy_doc и не приходят к view.
Я согласен что можно обойтись аггрегированием. А вместо вирутальных функций юзать делегаты. Или аггрегирование + наследование от интерфейса. Но это только workaround-ы, м-н подходит лучше.
Здравствуйте, VladD2, Вы писали:
VD>Хотелось бы услышать кто что думает по поводу необходимости подобных вещей в языках программирования.
VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Допустим, у нас есть интерфейс
interface IByteWriter
{
void put(byte b);
}
Данный интерфейс я могу использовать как "базовый класс" при построении некоторой реализации класса -- потока байтов.
Мне хочется добавить в этот интерфейс некоторые воспомогательные функции, реализация которых основана на абстрактных методах, определённых в интерфейсе.
Например.
Естественно, в C# это не легально, и я вынужден заменить интерфейс абстрактным классом.
abstract class IByteWriter
{
public abstract void put(byte b);
struct UintToBytes
{
public byte b1;
public byte b2;
public byte b3;
public byte b4;
public UintToBytes(uint u) { ... }
};
public void put(uint w)
{
UintToBytes x=new UintToBytes(w);
put(x.b1);
put(x.b2);
put(x.b3);
put(x.b4);
}
}
И тут я сталкиваюсь с проблеммой -- я могу унаследовать только от одного такого класса.
Если я в финальный класс захочу включить ещё один абстрактный базовый класс, например IStreamControl или IOutOfBandMessage, то у меня возникают траблы.
Наиболее точный перевод слова 'стафф' с английского, передающий практически 100% смысл этого слова — это русское слово 'хрень'. Так бы сразу и писали — базовая хрень
Ничего личного, просто повеселило употребление stuff в таком виде
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, VladD2, Вы писали:
VD>Хотелось бы услышать кто что думает по поводу необходимости подобных вещей в языках программирования.
1. Binding ASP.NET лучше всего работает с объектами, реализующими интерфейс ITypedList. Реализация этого интерфейса нетривиальна, но вполне возможна. Один раз реализовав TypedListImpl далее с помощью миксинов можно было бы подключать эту реализацию к нужным объектам. Сейчас это делается в базовом классе и с помощью копипейста переносится из проекта в проект.
2. При наличии соответствующих интерфейсов валидация тоже могла бы легко добавляться к объекту с помощью миксинов. Сейчас это делается так же в базовом классе.
3-й пример из той же серии — EditableObject. Причём, так как он всё равно работает только с абстрактными классами, а для них у нас уже есть реализация миксинов, то я в серьёз подумываю о том, чтобы прикрутить такую возможность к BLToolkit. Цель — высвободить базовый класс из пут фреймворка и оставить его в полном распоряжении разработчика.
4. Интерфейс IServiceProvider. Обычно каждый сервис реализует ещё и этот интерфейс, причём очень часто это простая делегация вызова к сервису, который зарегестрировал текущий сервис. Нужно реализовать всего лишь один метод, но с миксином было бы всё совсем просто.
VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Не знаю наколько это было бы резким упрощением. Чаще всего это делается один раз в базовом классе, а для резкого упрощения нужно искать часто решаемые однотипные задачи.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, VladD2, Вы писали:
VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Вот пример hello world из Harmonia:
Функциональность HtmlPanelT (html container) может
быть приделана к любому контейнеру виджетов:
Подмешивание методом curiously recurring template.
module samples.hello1;
// HTML Hello World.
import harmonia.ui.application;
import harmonia.ui.window;
import harmonia.html.view;
// HTML behavior can be attached to any container
// This time to the window.alias HtmlPanelT!(Window) HtmlWindow;void HelloWorldStart()
{
HtmlWindow w = new HtmlWindow;
w.html =
"<HTML back-color='edit info'
text-align=center
vertical-align=middle>Hello World!</HTML>";
w.state = Window.STATE.NORMAL;
}
static this()
{
Application.onStart = &HelloWorldStart;
}
Второй пример опять же из Harmonia:
Здесь мы имеем классический пример когда на один класс
навешиваются различные свойства/имплементации из некоего конструктора.
Писать такое каждый раз — тоскливо.
В D нет множественного наследования (что есть хорошо)
поэтому mixin'ы естественный путь.
А это вот немного не в тему но тем не менее:
Это mixin выполняющий роль macro в С/С++,
в данном случае template traverser создает
локальную (inner) типизированную функцию traverseEvent
Ну, на конец то разумный пример без явных багов в проектировании. Реализация вспомогательных методов и частиченая реализация интерфейса — это хороший аргумент.
Отаровенно говоря мне за последние 5 лет такого не попадалось, но все же.
Вот только в Nrmerle есть маросатрибут [Nemerle.DesignPatterns.ProxyAllMembers()] который, если я верно понимаю позволяет решить проблему автоматически созадав обертки над методами и делегировав их вызовы вложенному объекту.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Вот только в Nrmerle есть маросатрибут [Nemerle.DesignPatterns.ProxyAllMembers()] который, если я верно понимаю позволяет решить проблему автоматически созадав обертки над методами и делегировав их вызовы вложенному объекту.
Правда, если у нас нет развитой макросистемы, то мы тут же начинаем заниматься набиванием кода проксей. А если макросистема есть, то imho любую задачу, решаемую МН, можно решить и макросами. Разве что только это решение может иметь высокую плату за вход.
Здравствуйте, Anton V. Kolotaev, Вы писали:
AVK>Как решается средствами D проблема, когда примесям надо обращаться друг к другу?
AVK>Т.е. есть ли альтернатива следующему основанному на CRTP коду?
AVK>
Здравствуйте, Oyster, Вы писали:
O>Пример #2 O>…А один базовый класс тут невозможен потому, что коллекция, ясен пень, унаследована от List<T>.
А зачем List<T> не предназначен для наследования. Для создания собственных коллекций с контролируемым поведением есть Collection<T>. В крайнем случае — собственноручная реализация IList<T>+IList.
... << RSDN@Home 1.2.0 alpha rev. 650>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
O>>Пример #2 O>>…А один базовый класс тут невозможен потому, что коллекция, ясен пень, унаследована от List<T>.
_FR>А зачем List<T> не предназначен для наследования. Для создания собственных коллекций с контролируемым поведением есть Collection<T>. В крайнем случае — собственноручная реализация IList<T>+IList.
Ну ладно — не List<T> (это я всех обманул ), а наш класс для списков... сути не меняет.
Здравствуйте, VladD2, Вы писали:
VD>Хотелось бы услышать кто что думает по поводу необходимости подобных вещей в языках программирования.
VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Если выражаться общими словами, то можно сказать что множественное наследование рулит при описании ортогональной функциональности. Приведу пример из своей практики:
class LIKEVIEW_API Document : // Класс - Точка сборкиpublic Object<LV_IDCLASS_DOCUMENT_BASE, Document>, // Объект создаваемый фабрикойpublic RefCounter, // счётчик ссылокpublic Widget, // базовый элемент ГУИpublic Properties, // контейнер свойствpublic Shape, // фигура ГДИpublic IdGenerator, // генератор идентификаторов для сериализации связейpublic Port, // узел графа схемыpublic Link, // связь графа схемыpublic Executor // исполнитель действий
{
// ...
В данном примере показано наследование для основного класса Document библиотеки LikeView.
Библиотека предназначена для построения интерактивных 2D графических приложений, представляет собой архитектурный каркас и расширяемый набор действий-утилит. На ней можно с одинаковым успехом писать векторные редакторы, редакторы схем, диаграмм и пользовательский интерфейс.
Если последовать традиционным путём полиморфного разделения функционала, то получился бы комбинаторный взрыв классов.
Таким образом по эффективности множественное наследование можно сравнить с операцией умножения.
А любители агрегирования получат тонны формального кода.
Предупреждаю об одном нехорошем свойстве данной архитектуры — это непредсказуемость.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Шахтер, Вы писали:
VD>Ну, на конец то разумный пример без явных багов в проектировании. Реализация вспомогательных методов и частиченая реализация интерфейса — это хороший аргумент.
VD>Отаровенно говоря мне за последние 5 лет такого не попадалось, но все же.
VD>Вот только в Nrmerle есть маросатрибут [Nemerle.DesignPatterns.ProxyAllMembers()] который, если я верно понимаю позволяет решить проблему автоматически созадав обертки над методами и делегировав их вызовы вложенному объекту.
Танцы с бубном? А если вложенный обьект имеет виртульные функции которые необходимо переопределить в верхнем классе?
Тогда через делегаты и ручками, или вложенный обьект + верхний наследуем от интерфейса, сссылку на интерфейс передаем во вложенный обьект. Костыли и грабли налицо.
Множественное наследование отлично справляется когда есть иерархия классов и к некоторым из них нужно подключать функциональность которая не обеспечена основной иерархией. Т.е. если основная иерархия заточена под манипуляцию с данными, к одним классам добавляем визуализацию, к другим, что-то другое. Интерфейсы здесь не подходят т.к в одном флаконе требуются данные и реализация. Естественно проектировать классы надо с учетом того что они могут быть использованы во множ-наследовании.
Здравствуйте, Kluev, Вы писали:
K>Множественное наследование отлично справляется когда есть иерархия классов и к некоторым из них нужно подключать функциональность которая не обеспечена основной иерархией. Т.е. если основная иерархия заточена под манипуляцию с данными, к одним классам добавляем визуализацию, к другим, что-то другое. Интерфейсы здесь не подходят т.к в одном флаконе требуются данные и реализация. Естественно проектировать классы надо с учетом того что они могут быть использованы во множ-наследовании.
Цели, ради которых и было придумано множественное наследование, вроде бы ясны. Однако, на мой взгляд множественное наследование реализации (по крайней мере в том виде, в каком оно реализовано в C++) чревато граблями больше, чем дает преимуществ. С МН возникает, так называемая, Diamond problem.
class A {};
class B: public A {};
class C: public A {};
class D: public B, public C {};
В приведенном примере объект класса D будет в себе содержать два экземпляра базового подобъекта класса A. Один унаследован от B, другой -- от C. иногда это бывает полезно, но иногда требуется только один базовый подобъект. В C++ это контролируется при помощи виртуального наследования.
class A {};
class B: public virtual A {};
class C: public virtual A {};
class D: public B, public C {};
В этом примере экземпляр класса D будет содержать только один базовый подобъект класса A.
Представим, что класс D (который наследуется от B, C) разрабатывает один разработчик и он понимает, что ему требуется иметь один подобъект класса A в D, т.е. ему необходимо виртуальное наследование. Вот тут возникает затык, поскольку ему необходимо будет менять классы B и C, а они могут разрабатываться другим разработчиком и эти классы могут поставляться без исходников. Т.е. разработчик классов B и C должен был бы заранее предусмотреть, что от них будут множественно наследоваться.
Следует также отметить, что для выражения отношения is-a стоило бы всегда использовать виртуальное наследование. Просто потому, что ну не может одна сущность являться другой несколько раз! Невиртуальное наследование отражает скорее отношение между классами has-a. Вот забавный пример, когда задумывалось множественное именно невиртуальное наследование.
class wheel {};
class first_wheel: public wheel {};
class second_wheel: public wheel {};
class third_wheel: public wheel {};
class fourth_wheel: public wheel {};
class vehicle: public first_wheel, public second_wheel, public third_wheel, public fourth_wheel {};
Здесь транспортное средство наследуется от четырех колес. Полагаю, что этот пример было бы более естественно переписать с использованием агрегации, а не наследования. Транспортное средство ведь не есть колесо четырежды, -- оно имеет четыре колеса.
Однако лично я редко видел, чтобы активно использовалось виртуальное наследование. По крайней в примерах кода в данном топике его по-моему не было ниразу. Должно быть, это из-за того, что виртуальное наследование бесплатно не дается и приведение к виртуальному базовому подобъекту менее эффективно, чем к невиртуальному. А может быть просто сила привычки.
Есть еще одна проблема с виртуальным наследованием: оно вынуждает классу знать не только о своих непосредственных предках, а и о всех своих виртуальных предках.
class A {
public:
A(int) {}
};
class B: public virtual A {
public:
B(): A(1) {}
};
class C: public virtual A {
public:
C(): A(2) {}
};
class D: public B, public C {
public:
D(): B(), C(), A(3) {}
};
При создании экземпляра класса D и инициализации единственного базового подобъекта A, какой конструктор должен Вызваться: A(1) или A(2)? Здесь решение принимается в классе D. Об этом можно не беспокоиться, если класс, унаследованный виртуально, имеет конструктор по умолчанию. Но в общем случае это добавляет лишние зависимости.
Резюмирую: для того, чтобы безопасно использовать МН в C++ необходимо чтобы у всех классов были конструкторы по умолчанию и все классы должны наследоваться виртуально. Мне кажется, что эти требования весьма жесткие.
Здравствуйте, dshe, Вы писали:
D>Цели, ради которых и было придумано множественное наследование, вроде бы ясны. Однако, на мой взгляд множественное наследование реализации (по крайней мере в том виде, в каком оно реализовано в C++) чревато граблями больше, чем дает преимуществ.
Граблями черевато абсолютно все даже int,
надо юзать фичи там где они уместны и не юзать там где они неуместны.
Если м-н юзается чтобы соединить две ветвистые иерархии классов, то грабли обеспечены.
М-н совершенно безопасно если все подмешиваемые к одной иерархии классы sealed (т.е. спроектированы так чтобы не иметь наследников).
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, WolfHound, Вы писали:
VD>Ты мог бы сформировать простой и наглядный пример?
Зыыыыыы
public interface IControl
{
#region General Properies
/// <summary>
/// Нечто, присоедененное к элементу управления
/// </summary>
[ReflectedDependency]
[Browsable(false)]
object Tag
{get;set;}
#endregion
#region Information Properties
#endregion
}
public interface IButton : IControl
{
string Text
{get;set;}
event EventHandler Click;
}
ну и тд..
А теперь все это добро имплементим на основе WinForms
Здравствуйте, WoldemaR, Вы писали:
WR>Если выражаться общими словами, то можно сказать что множественное наследование рулит при описании ортогональной функциональности. Приведу пример из своей практики: WR>
WR>class LIKEVIEW_API Document : // Класс - Точка сборки
WR> public Object<LV_IDCLASS_DOCUMENT_BASE, Document>, // Объект создаваемый фабрикой
WR> public RefCounter, // счётчик ссылок
WR> public Widget, // базовый элемент ГУИ
WR> public Properties, // контейнер свойств
WR> public Shape, // фигура ГДИ
WR> public IdGenerator, // генератор идентификаторов для сериализации связей
WR> public Port, // узел графа схемы
WR> public Link, // связь графа схемы
WR> public Executor // исполнитель действий
WR>{
WR>// ...
WR>
RefCounter в лес за недадобностью.
Document не имеет быть права контролом (Widget-ом) с точки зрения дизайна, в прочем как и фигурой (Shape) и исполнителем действий (Executor).
И того при нормальном дизайте этот класс будет вглядеть как-то так:
// Объект создаваемый фабрикой
[ClassFabric(X)]
class Document : SchemaNode
{
Properties Properties { get { Properties(); } }
}
Вывод — очередная демонстрация того как МН приводит к ужасному дизайну "гэть в кучу".
WR>В данном примере показано наследование для основного класса Document библиотеки LikeView. WR>Библиотека предназначена для построения интерактивных 2D графических приложений, представляет собой архитектурный каркас и расширяемый набор действий-утилит. На ней можно с одинаковым успехом писать векторные редакторы, редакторы схем, диаграмм и пользовательский интерфейс.
WR>Если последовать традиционным путём полиморфного разделения функционала, то получился бы комбинаторный взрыв классов.
Не... получился бы хороший дизайн намного более подходящий для дольнейшего развития.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.