Re[6]: Шаблоны - не выход. Вернее, не всегда выход.
От: Voblin Россия http://maslyaew.narod.ru/
Дата: 13.05.03 11:31
Оценка:
Здравствуйте, Akzhan, Вы писали:

A>В варианте mixin architecture это выглядит так же, но более строго. Поэтому все возможные ошибки ловятся ещё на этапе компиляции. Единственное разлчие — невозможность динамического изменения типа — мутации. Другое дело, что я считаю мутации объектов вредной техникой с точки зрения поддержки кода. Но если нужны мутации, то надо использовать паттерн объект-стратегия.


A>
A>class CMyObject : 
A>  public CCompoundObject, CPersonT<CMyObject>, CMaleT<CMyObject>
A>{
A>};

A>CMyObject myObj;
A>


Тоже хороший подход. Сразу бросается в глаза вот что:
1. Делается на базе шаблонов. То есть мы заставляем компилятор генерировать некий "промежуточный" исходник, в котором все наши используемые сочетания обявлены явно и жёстко. Мало того, что генерируется большой объём кода (это не страшно, кто сейчас считает мегабайты?), но и за кадром может остаться множество
полезных сочетаний.
2. Мутация бывает очень полезна. Поясняю. Например, пользователь заводит карточку сотрудника. Пол он ещё не ввёл, и поэтому объект не принадлежит ни классу CMale, ни классу CFemale. Выбирает пол сотрудника — "Male". С этого момента неплохо было бы сменить тип редактируемого объекта на (CPerson, CMale).
3. В маленьком примерчике это смотрится нормально. Но если на секунду себе представить, как будет выглядеть система mixin-ов (и особенно результирующая система классов, получающаяся после компиляции шаблонов) какой-нибудь реальной системы, то станет страшно.

Пример из жизни.

Сотрудник — это живой человек (Person), мужчина или женщина
Person может быть сотрудником, а может и не быть сотрудником
Person может быть контрагентом, который, в свою очередь, может быть поставщиком и покупателем (либо и тем, и другим сразу)
Контрагент может быть физ. или юр. лицом (юр. лицо — не Person)
Юр. и физ. лицо бывают резидентами и нерезидентами
В выписываемый счёт в колонку "Item" мы должны иметь возможность вписать сотрудника, товар, услугу, ОС, НМА, материал.
Сотрудник может быть ресурсом (производственный рабочий, бизнес-консультант), а может и не быть ресурсом (бухгалтер, директор).
Ресурс может быть продаваемым (должна быть назначена цена) либо непродаваемым.
Сотрудник может являться пользователем.
Контрагент может являться пользователем (через web-интерфейс).
В базу данных заносятся родственники сотрудников, которые, как правило, сотрудниками не являются, но возможны исключения.
Ой-ой-ой! Чуть не забыл про акционеров!
-----------------------
Уффф... и это только маленький кусок системы!
Кто скажет, что пример надуманный?

Как тут выстроить систему классов? Что сделать mixin-ом и во что подмешивать?

Самое интересное, что если абстрагироваться от необходимости писать бизнес-логику на ОО-языке программирования, структура базы данных такой системы в общих чертах рисуется без особого интеллектуального перенапряжения. Но уложить такое в классы — свихнёшься.
Re[6]: Ой
От: Voblin Россия http://maslyaew.narod.ru/
Дата: 13.05.03 12:00
Оценка:
Здравствуйте, Kluev, Вы писали:

K>Как видите интерфейсы IFruit, IColored, IColoredFruit не связаны узами наследования однако все работает


По-моему, как раз получается связано наследованием. Или я чего-то не понял?

Вы вообще поняли, насколько СТРАШНЫМ получился код? Насколько запутанная у него логика работы? Насколько тяжёлым будет его сопровождение?

За возможность хранить или не хранить информацию о цвете в объекте "Фрукт" мы заплатили такую высокую цену, что победа оказалась пиррова.

А вообще, спасибо за иллюстрацию применения mixin-ов. Это было очень любопытно
Re[7]: Ой
От: Kluev  
Дата: 13.05.03 14:22
Оценка: +2
Здравствуйте, Voblin, Вы писали:

V>Здравствуйте, Kluev, Вы писали:


K>Как видите интерфейсы IFruit, IColored, IColoredFruit не связаны узами наследования однако все работает


V>По-моему, как раз получается связано наследованием. Или я чего-то не понял?

Не связаны — ни один из интерфейсов не наследуется от другого. Реализация, однако, связана отношениями использования.
cchar_t* ColoredFruit::_IColoredFruit_name() {
   static char buf[80];
   sprintf( buf, "%s %s", self().IColored::color_get(), self().IFruit::kind() );
   return buf;
}

Сами понимаете, что "ЦветнойФрукт" не может существовать без "Фрукта" и "Цвета"

V>Вы вообще поняли, насколько СТРАШНЫМ получился код? Насколько запутанная у него логика работы? Насколько тяжёлым будет его сопровождение?

Обычный код, ничего страшного. Логика работы крайне простая т.к. кроме отношений "использования" больше ничего не не используется. Сопровождение и чтение кода наоборот будет лекгим как перышко за счет использования нотации: _Classname_attribname_functionname:
self().IColored::color_get(), self().IFruit::kind() — компилятор сразу покажет какие функции должен поддерживать "верхний" обьект, а по именам легко определить название интерфейсов.

V>За возможность хранить или не хранить информацию о цвете в объекте "Фрукт" мы заплатили такую высокую цену, что победа оказалась пиррова.

О какой цене идет речь?

V>А вообще, спасибо за иллюстрацию применения mixin-ов. Это было очень любопытно


Я долго ломал голову чтобы придумать абстрактный пример в котором не пришлось бы добавлять код в классах реализации. В реальной жизни увы не получится собирать классы из кирпичиков. Почти всегда требуется дополнительная реализация. К примеру если бы мы завели интерфейс
struct IObject {
   const char* _IObject_fullName_get() = 0;
}

То в классах реализации без изменений не обойтись
struct MyApple :
    Apple<MyApple>,
    Colored<MyApple>,
    ColoredFruit<MyApple>,
    Part<IObject,MyApple>
{
    const char* _IObject_fullName_get() {
        static char buf[100];
        sprintf( buf,"%s %s Fruit", color_get(), kind() );
        return buf; // red apple fruit
    }
};


То что предлагаете вы будет работать только в простых случаях не представляющих интереса. Я никогда не поверю в то, что следующий код будет работать
class LinuxWindow;
class Win32Window;
class DBView;

var dbvLnx as (LinuxWindow,DBView)
var dbvWin as (Win32Window,DBView)
Re[8]: Ой
От: Kluev  
Дата: 13.05.03 15:01
Оценка: 4 (2)
Здравствуйте, Kluev, Вы писали:

Забыл написать про наследование. Оно используется только для склеивания отдельных кусков в класс реализацию. Такой подход позволяет полностью абстрагироватся от способа реализации интерфейса, Что было показана на примере MyApple и MyMango.

Если вы опасаетесь что шаблоны непомерно раздуют код можно ограничить их использование следующим образом:

// Тяжелый класс (не шаблонный)
class MyObject : public IFoo, public IXoo, public IZoo, public IMoo {
protected:
// этот класс требует для своей работы следующие интерфейсы:
    virtual IxFoo& _MyObject_IxFoo() = 0;
    virtual IxZoo& _MyObject_IxZoo() = 0;

public:

    // 500 функций опущено
};

// Легкая шаблонная обертка
template <class T>
struct TMyObject : Part<MyObject,T> {
// этот код гарантирует поддержку классом Т интерфейсов IxFoo, IxZoo
    IxFoo& _MyObject_IxFoo() { return self(); }
    IxZoo& _MyObject_IxFoo() { return self(); }
};

// Теперь можно собирать по кусочкам:
struct Test1 :
    TMyObject<Test>,
    TMyIxFooImpl<Test1>,
    TMyIxZooImpl<Test2>
{
    // ..............
};

struct Test2 :
    TMyObject<Test2>,
    TMy_Another_IxFoo_And_IxZoo_Impl_in_single_class<Test2>
{
    // ..............
};


Это добавит к разарботке дополнительный шаг (необходимость написания обертки), зато выгоды моут быть потрясающими. К примеру несколько программистов независимо друг от друга разарабатывают очень сложный класс. Каждый их них пишет свою часть, учитывая что его коллеги занимаются реализацией остальных частей которые будут доступны через соответствующие интерфейсы. Пока класс не написан каждый программер пишет для себя урезанную-тестовую реализацию остальных интерфейсов (обычно это не занимает много времени)

// Кусок класса который пишет Иванов
class CIvanovFinalPart : public IIvanovPart {
protected:
    virtual ISidorovPart &_CIvanovPart_SidorovPart() = 0;
    virtual IPetrovPart &_CIvanovPart_SidorovPart() = 0;
};

// Реализация для тестирования
struct CIVanovTest :
    TIvanovFinalPart<CIVanovTest>,
    TSidorovDummyPart<CIVanovTest>,
    TPetrovDummyPart<CIVanovTest>
{
    // .......................
};


Когда все части готовы, они обьединяются в одном классе, а тестовые реализации выкидываются

// окончательная реализация:
class CFinalUltimateClass :
    public TIvanovFinalPart<CFinalUltimateClass>,
    public TSidorovFinalPart<CFinalUltimateClass>,
    public TPetrovFinalPart<CFinalUltimateClass>
{
    // .. здесь могут быть решены мелкие проблеммы
};
Re[8]: Ой
От: Voblin Россия http://maslyaew.narod.ru/
Дата: 13.05.03 16:24
Оценка:
Здравствуйте, Kluev, Вы писали:

V>По-моему, как раз получается связано наследованием. Или я чего-то не понял?

K>Не связаны — ни один из интерфейсов не наследуется от другого. Реализация, однако, связана отношениями использования.
Пардон

V>Вы вообще поняли, насколько СТРАШНЫМ получился код? Насколько запутанная у него логика работы? Насколько тяжёлым будет его сопровождение?

K>Обычный код, ничего страшного. Логика работы крайне простая т.к. кроме отношений "использования" больше ничего не не используется. Сопровождение и чтение кода наоборот будет лекгим как перышко за счет использования нотации: _Classname_attribname_functionname:
K>self().IColored::color_get(), self().IFruit::kind() — компилятор сразу покажет какие функции должен поддерживать "верхний" обьект, а по именам легко определить название интерфейсов.
Может быть это мне показалось с непривычки, но когда один template наползает на другой template и юзает его через третий template, мне становится дурно. А ошибки в этом ловить...

V>За возможность хранить или не хранить информацию о цвете в объекте "Фрукт" мы заплатили такую высокую цену, что победа оказалась пиррова.

K>О какой цене идет речь?
А если, допустим, под объектом Colored будет скрываться супермощная система методов, занимающая в исходниках мегабайт и поддерживающая всю функциональность работы с цветом, заложенную в фотошоп?
Мы, такие лопоухие, ничего не подозревая, напишем:

struct MyApple ...

struct MyMango ...

struct MyOrange ...

struct MyHome ...

// ... и т.д.


Ой, а почему на CD программа не помещается?

K>То что предлагаете вы будет работать только в простых случаях не представляющих интереса. Я никогда не поверю в то, что следующий код будет работать

K>
K>class LinuxWindow;
K>class Win32Window;
K>class DBView;

K>var dbvLnx as (LinuxWindow,DBView)
K>var dbvWin as (Win32Window,DBView)
K>

А почему нет? Если логику грамотно прописать, то должно.




Вот как то же самое может выглядеть при множественном наследовании:
class CFruit { // Фрукт
 var Kind as string;
 function name() as string {
   if Result = NULL { // Отдаёмся на откуп "наследникам"
     Result = Kind;
   }
 }
}

class CColored { // Нечто цветное
 var Color as string;
}

function (CFruit, CColored)::name() as string { // Доопределяем функцию CFruit::name()
  Result = Color + " " + Kind;
}

procedure TestColoredFruits(Other as CFruit) {  // Тестовая процедурка
 Var Fruit as CFruit,
     Colored as CColored,
     ColoredFruit as (CColored, CFruit);
 Fruit.Kind = "Mango";
 ColoredFruit.Kind = "Apple";
 ColoredFruit.Color = "Green";

 App.MsgBox(Fruit.name()); // "Mango"
 App.MsgBox(ColoredFruit.name()); // "Green Apple"
 App.MsgBox(Other.name()); // Зависит от того, что прислали.
     // Может выдать, например, "Plum" или "Yellow Banana"

 // А теперь нам хочется закричать страшным голосом, если Other зелёный
 ifcast Other as CColored { // такой специальный оператор
  if lower(Other.Color) = "green" { // здесь компилятор уже знает, что у Other есть Color
   App.MsgBox("Фууууу! Какая гадость! " + Other.name() + "!");
  }
 }
}


Всё просто и элегантно.

Кстати, в моём примерчике высвечивается такая проблема: если объявлен ещё один класс CQuality, то функция name() будет работать абы как:

class CQuality {
 var Sort as String;
}

function (CFruit, CQuality)::name() as string { // Тоже доопределяем функцию CFruit::name()
  Result = Sort + " " + Kind;
}


Что покажет такой код

var AllTogather as (CColored, CFruit, CQuality);
AllTogather.Kind = "Grape";
AllTogather.Color = "Red";
AllTogather.Quality = "Rotten";
App.MsgBox(AllTogather.name());


"Red Grape" или "Rotten Grape" ?

Не бейте меня ногами. Знаю, что проблема есть. Но это проблема, на мой взгляд, скорее методического плана. Думаю, решение для неё есть.
Re[4]: Статья про развитие идей ООП. Жду комментариев.
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 13.05.03 16:39
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Сейчас наоборот наблюдается деградация языков программирования к более простым вариантам

А>(видимо программист пошел совсем недалекий).

В точку. Вернее, концентрация "далёких" уменьшилась. А ещё — вследствие притягивания псевдопромышленной модели организации труда в софтостроение.

А>Наглядный пример Java и VB — полные ничтожества в языковом плане, никаких понят... тьфу возможностей,

А>однако ведь лидеры промышленной разработки.

Ключевое слово — "простота", а ещё — "специализация". А ещё — магическая фраза "MS-технология", "Современная технология" и т.п.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[6]: Mixins - вариант множественного наследования?
От: WolfHound  
Дата: 13.05.03 16:41
Оценка: 12 (1)
Здравствуйте, Kluev, Вы писали:

K>Почему же нет возможности? Немного смекалки и все будет

А если еще подумать то можно прикрутить контроль
template<class I, class T>struct InterfaceCheck{};
#define INTERFACE_CHECK(cur, test)\
STATIC_CHECK(Loki::SuperSubclass<test, T>::value, if_class_is_##cur##_then_it_must_be_##test)

template <class I, class T>
struct Part
    :I
{
    T& self() { return *static_cast<T*>(this); }
private:
    typedef InterfaceCheck<I, T> dummy;
};

struct IColored{};
struct IFruit{};

struct IColoredFruit{};
template<class T>
struct InterfaceCheck<IColoredFruit, T>
{
    INTERFACE_CHECK(IColoredFruit, IColored)
    INTERFACE_CHECK(IColoredFruit, IFruit)
};

Теперь если попробовать создать обьект содержащий IColoredFruit но не содержащий IFruit или IColored то компилятор будет громко ругаться.
ЗЫ Писал без компилятора.
ЗЗЫ Ну что за дурацкая привычка _*?
... << RSDN@Home 1.0 beta 6a >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[8]: Ой
От: WolfHound  
Дата: 13.05.03 16:41
Оценка:
Здравствуйте, Kluev, Вы писали:

K>То что предлагаете вы будет работать только в простых случаях не представляющих интереса. Я никогда не поверю в то, что следующий код будет работать

K>
K>class LinuxWindow;
K>class Win32Window;
K>class DBView;

K>var dbvLnx as (LinuxWindow,DBView)
K>var dbvWin as (Win32Window,DBView)
K>

Я тоже.
... << RSDN@Home 1.0 beta 6a >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[9]: Ой
От: WolfHound  
Дата: 13.05.03 16:41
Оценка:
Здравствуйте, Kluev, Вы писали:

K>К примеру несколько программистов независимо друг от друга разарабатывают очень сложный класс. Каждый их них пишет свою часть, учитывая что его коллеги занимаются реализацией остальных частей которые будут доступны через соответствующие интерфейсы. Пока класс не написан каждый программер пишет для себя урезанную-тестовую реализацию остальных интерфейсов (обычно это не занимает много времени)

А вот таких эксперементов лучше избегать. Практика показывает что практически любой класс (кроме мелких) можно разбить на несколько мелких.
... << RSDN@Home 1.0 beta 6a >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[9]: Ой
От: WolfHound  
Дата: 13.05.03 17:01
Оценка:
Здравствуйте, Voblin, Вы писали:

V>Может быть это мне показалось с непривычки, но когда один template наползает на другой template и юзает его через третий template, мне становится дурно. А ошибки в этом ловить...

С непривычки. А ошибки тут ловить довольно просто, а если учесть что прибив ошибку в одном месте то она испавится везде...

V>А если, допустим, под объектом Colored будет скрываться супермощная система методов, занимающая в исходниках мегабайт и поддерживающая всю функциональность работы с цветом, заложенную в фотошоп?

V>Мы, такие лопоухие, ничего не подозревая, напишем:
А если мы не лопухи и немного переделаем то
template <class T>
struct Self{
    T& self() { return *static_cast<T*>(this); }
};

template <class T>
struct Mango:Self<T>, IFruit {
    cchar_t* _IFruit_kind() { return "mango"; }
};
struct ColoredImpl:IColored//Толстая реализация
{
    std::string    _m_color;

    cchar_t* _IColored_color_get() { return _m_color.c_str(); }
    void _IColored_color_set( cchar_t *color ) { _m_color = color; }
};
template <class T>
struct Colored :Self<T>, ColoredImpl{};


V>Не бейте меня ногами. Знаю, что проблема есть.

Во-во и мы о томже...
V>Но это проблема, на мой взгляд, скорее методического плана. Думаю, решение для неё есть.
Нет это концептуальная проблема.
... << RSDN@Home 1.0 beta 6a >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[4]: Статья про развитие идей ООП. Жду комментариев.
От: WolfHound  
Дата: 13.05.03 17:06
Оценка: +1
Здравствуйте, <Аноним>, Вы писали:

А>Даже примитивный С++ с его жалкими темплейтами оказался слишком сложен — и вот вам С#!

Я на C# хочу в основном из-за рефлекшена и некоторых других полезных возможностей.
ЗЫ С++ не такой уж и примитивный (я имею в виде С++, а не конкретные реализации) если уметь пользоваться.
... << RSDN@Home 1.0 beta 6a >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[9]: Ой
От: Voblin Россия http://maslyaew.narod.ru/
Дата: 13.05.03 17:11
Оценка:
Здравствуйте, Kluev, Вы писали:

K>// Легкая шаблонная обертка

K>template <class T>
K>struct TMyObject : Part<MyObject,T> {
K>// этот код гарантирует поддержку классом Т интерфейсов IxFoo, IxZoo
K> IxFoo& _MyObject_IxFoo() { return self(); }
K> IxZoo& _MyObject_IxFoo() { return self(); }
K>};

K>// Теперь можно собирать по кусочкам:

K>struct Test1 :
K> TMyObject<Test>,
K> TMyIxFooImpl<Test1>,
K> TMyIxZooImpl<Test2>
K>{
K> // ..............
K>};

K>struct Test2 :

K> TMyObject<Test2>,
K> TMy_Another_IxFoo_And_IxZoo_Impl_in_single_class<Test2>
K>{
K> // ..............
K>};
K>[/ccode]

Безусловно, это очень интересно, полезно и может быть всячески рекомендовано всем, кто пишет на С++.

Несколько соображений:
1. Не все задачи имеет смысл решать на С++. И с этим можно только смириться.
2. Главная проблема всё равно остаётся нерешённой. Для того, чтобы покрыть всё разнообразие объектов, встречающихся в задаче, мы обязаны все их классы объявить в программе явно. В некоторых случаях это хорошо, но иногда сильно напрягает.
3. Может ли случиться так, что код, реализующий интерфейс IxFoo должен будет вести себя по-разному в зависимости от того, сидит ли с ним под одной крышей IxZoo? Предположим, это случилось. Как быть? Разруливать в теле класса Test1? А потом копированием/вставкой тянуть в Test2?
4. Что у нас с полиморфизьмом? Как сделать так, чтобы функция name() простого фрукта вернула "Apple", а цветного фрукта — "Green Apple"?

Думаю, mixins и то, что описано в статье в равной степени имеют право быть.
Re[7]: Шаблоны - не выход. Вернее, не всегда выход.
От: Akzhan Россия http://www.akzhan.midi.ru/devcorner/
Дата: 13.05.03 18:22
Оценка:
Здравствуйте, Voblin, Вы писали:

V>Тоже хороший подход. Сразу бросается в глаза вот что:

V>1. Делается на базе шаблонов. То есть мы заставляем компилятор генерировать некий "промежуточный" исходник, в котором все наши используемые сочетания обявлены явно и жёстко. Мало того, что генерируется большой объём кода (это не страшно, кто сейчас считает мегабайты?), но и за кадром может остаться множество
V>полезных сочетаний.

Шаблоны как таковые (при правильном подходе) являются средстством резко уменьшить объём кода. Посмотрите на библиотеки MFC и WTL. При решении одной и той же задачи объём кода разнится на порядки. Причём WTL/ATL использует именно mixin-подход.

V>2. Мутация бывает очень полезна. Поясняю. Например, пользователь заводит карточку сотрудника. Пол он ещё не ввёл, и поэтому объект не принадлежит ни классу CMale, ни классу CFemale. Выбирает пол сотрудника — "Male". С этого момента неплохо было бы сменить тип редактируемого объекта на (CPerson, CMale).


В терминах реляционных баз данных мы просто вносим уточнение на уровне отдельного атрибута. При закачке в оперативную память (swizzling) генерируется объект необходимого типа (всё равно "Персона"). Удобно для случая stateless objects.

Если же рассматривать, почему: Вы подобрали плохой пример — здесь удобнее не наследование, а включение.
Так, "Персона" имеет необязательное свойство "Пол". Это удобнее, и не надо вводить спорную концепцию.
С уважением,
Акжан, http://www.akzhan.midi.ru/devcorner/ — мой уголок разработчика
Re[9]: Ой
От: Kluev  
Дата: 13.05.03 18:58
Оценка:
Здравствуйте, Voblin, Вы писали:

K>О какой цене идет речь?

V>А если, допустим, под объектом Colored будет скрываться супермощная система методов, занимающая в исходниках мегабайт и поддерживающая всю функциональность работы с цветом, заложенную в фотошоп?
V>Мы, такие лопоухие, ничего не подозревая, напишем:
V>
V>struct MyApple ...
V>struct MyMango ...
V>struct MyOrange ...
V>struct MyHome ...
V>// ... и т.д.
V>

V>Ой, а почему на CD программа не помещается?

Малореальная ситуация. Перед тем как юзать обычно сырцы\доку смотрят, а иначе как?. К тому-же это не проблема времени выполнения


K>То что предлагаете вы будет работать только в простых случаях не представляющих интереса. Я никогда не поверю в то, что следующий код будет работать

K>
K>class LinuxWindow;
K>class Win32Window;
K>class DBView;

K>var dbvLnx as (LinuxWindow,DBView)
K>var dbvWin as (Win32Window,DBView)
K>

V>А почему нет? Если логику грамотно прописать, то должно.
V>


Вот ситуация: DBView создает несколько дочерних форм. Как он сможет их создать будучи полностью отделенным от LinuxWindow или Win32Window? Очевидно, что практически невозможно обеспечить единство интерфейсов для LinuxWindow и Win32Window. В этом случае нам нужна платформенно-независимая библиотека. Но смотрите:
class LinuxWindow;
class Win32Window;
class PlatformIndependentWindow;
class DBView;

var dbvLnx as (LinuxWindow,DBView) // не подойдет
var dbvLnx as (Win32Window,DBView) // не подойдет
var dbvLnx as (PlatformIndependentWindow,DBView) // сойдет

В итоге имеем толко один вариант, а раз он один то се-таки удобнее делать так
class DBView : public PlatformIndependentWindow {....};

V>Вот как то же самое может выглядеть при множественном наследовании:

V>
V>class CFruit { // Фрукт
V> var Kind as string;
V> function name() as string {
V>   if Result = NULL { // Отдаёмся на откуп "наследникам"
V>     Result = Kind;
V>   }
V> }
V>}

V>class CColored { // Нечто цветное
V> var Color as string;
V>}

V>function (CFruit, CColored)::name() as string { // Доопределяем функцию CFruit::name()
V>  Result = Color + " " + Kind;
V>}
V>


Выглядит красиво но не без проблем. Фактически — это альтернативный способ записи множественного наследования, Следующий код поясняет это:

function (CFruit, CColored)::name() as string { // Доопределяем функцию CFruit::name()
  Result = this->CColored::Color + " " + this->CFruit::Kind;
}


Видно что функция function (CFruit, CColored)::name() жестко привязана к конкретным реализациям классов CFruit, CColored — наследования то у вас нет
В этом случае прийдется постоянно писать что-то типа (ведь нет никакой связи между классами)
string (CApple,CFruit,CColored)::name()
string (COrange,CFruit,CColored)::name()
Боюсь программеры этого не одобрят и не поймут


V>Всё просто и элегантно.


V>Кстати, в моём примерчике высвечивается такая проблема: если объявлен ещё один класс CQuality, то функция name() будет работать абы как:


V>
V>class CQuality {
V> var Sort as String;
V>}

V>function (CFruit, CQuality)::name() as string { // Тоже доопределяем функцию CFruit::name()
V>  Result = Sort + " " + Kind;
V>}
V>


V>Что покажет такой код


V>
V>var AllTogather as (CColored, CFruit, CQuality);
V>AllTogather.Kind = "Grape";
V>AllTogather.Color = "Red";
V>AllTogather.Quality = "Rotten";
V>App.MsgBox(AllTogather.name());
V>


V>"Red Grape" или "Rotten Grape" ?


V>Не бейте меня ногами. Знаю, что проблема есть. Но это проблема, на мой взгляд, скорее методического плана. Думаю, решение для неё есть.


Эта проблемма легко решается наследованием как я уже показывал:
struct MyApple :
    Apple<MyApple>,
    Colored<MyApple>,
    ColoredFruit<MyApple>,
    Part<IObject,MyApple>
{
    string _IObject_fullName_get() {
        return color_get() + " " + kind() + " fruit";
    }
};

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

V>Несколько соображений:

V>1. Не все задачи имеет смысл решать на С++. И с этим можно только смириться.
Никто и не спорит. Если бы вы что-нибудь SQL подобное описывали вам бы никто и не возразил, а у вас типичная задача для С++ (уровня библиотеки шаблонов и интерфейсов)
V>2. Главная проблема всё равно остаётся нерешённой. Для того, чтобы покрыть всё разнообразие объектов, встречающихся в задаче, мы обязаны все их классы объявить в программе явно. В некоторых случаях это хорошо, но иногда сильно напрягает.
Шаблоны спасают.

Я как-то работал с языком Express-1 (язык описания данных) он поддерживает наследование в вашем стиле, т.е. обьекты в нем обьявляются так: #EdgeId=(Object("Dummy"),GeometricObject(),EdgeObject(#PointID,#PointID,#CurveId)) — в скобках вызовы конструкторов составного обьекта. Таким макаром можно наплодить дикое количество типов. Вопрос, что потом со всем этим делать?

V>3. Может ли случиться так, что код, реализующий интерфейс IxFoo должен будет вести себя по-разному в зависимости от того, сидит ли с ним под одной крышей IxZoo? Предположим, это случилось. Как быть? Разруливать в теле класса Test1? А потом копированием/вставкой тянуть в Test2?

Пример приведите, тогда и помозгуем, а так абстрактно слишком
V>4. Что у нас с полиморфизьмом? Как сделать так, чтобы функция name() простого фрукта вернула "Apple", а цветного фрукта — "Green Apple"?
Реализацией метода name в финальном классе-реализации. это аналогично добавлению метода (CFruit,CColored)::name()
Re: Статья про развитие идей ООП. Жду комментариев.
От: Akzhan Россия http://www.akzhan.midi.ru/devcorner/
Дата: 14.05.03 07:08
Оценка: 6 (1)
Здравствуйте, Voblin, Вы писали:

http://www.rsdn.ru/Forum/Message.aspx?mid=266637&amp;only=1
Автор: PM
Дата: 14.05.03
С уважением,
Акжан, http://www.akzhan.midi.ru/devcorner/ — мой уголок разработчика
Re: Статья про развитие идей ООП. Жду комментариев.
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.05.03 07:35
Оценка: 3 (1)
Здравствуйте, Voblin, Вы писали:

V>Может быть, кому-нибудь будет интересно.

V>http://voblin.nm.ru/objects_classes.dhtml
V>Сразу вопрос: стоит ли это опубликовать в RSDN?
Что-то я так и не понял, чем же это отличается от "классического ООП в духе Страуструпа".
Ну, вот есть у нас классы. (Все классически). Ну вот может объект принадлежать к нескольким классам — тоже классика. (Вообще говоря, любой ациклический граф соответствует валидной схеме наследования классов).
Связанность цепочек наследования классов — это жупел. Если собака является живым существом, то интерфейс собаки существенно зависит от того, что она живое существо. Говоря проще, появление собаки Айбо (не знаю, кто это такой) нарушает картину только в силу некомпетентности архитектора. В хорошей модели способность собаки гавкать и способность гадить не связаны между собой отношенями наследования:
interface IBark {
  void Bark();
}
interface ICreature {
  void Live();
}
interface IGadget {
  void Execute();
}

class LiveDog : public ICreature, IBark {};
class ElectronicDog : public IGadget, IBark {};


Требование

присутствия возможности задания перечня классов непосредственно для каждого объекта

давно удовлетворено в классике. Никто не мешает писать так:
  class _useonceforIbo_ : piblic IGadget, IBark{} *Ibo = new _useonceforIbo_();

хотя так делать и не стоит. А не стоит потому, что теперь мы получили объект уникального класса. Легкая модификация:
  class _useonceforIbo_ : piblic IGadget, IBark{} *Ibo = new _useonceforIbo_();
  class _useonceforIbo2_ : piblic IGadget, IBark{} *IboSon = new _useonceforIbo2_();

и упс! У нас два объекта различных классов. Хотя набор интерфейсов в точности совпадает.
Так что на диаграмме надо бы стереть слова "Non-classic".
Ага, дальше мы собрались отказаться от наследования и вместо "реального" класса XY: public X, Y {} ввести виртуальный класс XY. Пардон, а чем он так радикально отличается от своего "реального" тезки?

Вооружившись комбинаторикой, можно вспомнить, что количество сочетаний N классов, выбранных среди K существующих (без учета порядка), равно С(K, N) = K!/(N!*(K-N)!))


Обычно создают родительский класс clPersistent, имеющий методы Read() и Save(), и классы clClient и clOrder порождают (возможно, косвенно) от Persistent.
Мы же, вооружившись С++, тоже создадим
class clPersistent {
  virtual void Read()=0;
  virtual void Save()=0;
}

и классы
class clOrder {
  SomeOrderMethod();
}
class clClient {
  SomeClientMethod();
}

Запись реквизитов контрагента в базу данных будет производиться в процедуре Save() класса clPersistentClient: public clPersistent, clClient{}, а реквизитов заказа — в процедуре, реализованной для clPersistentOrder: public clPersistent, clOrder{}.
Ну да, анси паскаль отдыхает. Впрочем, мы и раньше это знали.

Правда, нам не удастся с такой легкостью отказаться от абстрактного класса clPersistent, который только что был нам нужен — он просто не может быть конкретным. Ведь он не знает, что нужно читать или сохранять. Но мы поступим хитро, и, чтобы разработчик не рисковал забыть реализовать спопоб записи ордеров, переделаем класс так, чтобы его методы были просто пустыми. Правда, теперь забывчивый разработчик сможет скомпилировать программу без единой ошибки от компилятора (которая выдается С++) и даже запустить ее без аварийного завершения (стандартное поведение Delphi. Впрочем, в Delphi мы полностью свободны в выборе того, что делать при возниковении Abstract Call во время выполнения). Пользователю останется только гадать, почему его заказы не пишутся в базу.

Я не очень понял пассаж про вызов метода класса XZ для объекта класса XY. Вот если бы наш объект был класса XYZ, тогда бы эта кандидатура была бы оправдана.
Ок, давайте рассмотрим такую ситуацию:
1. У нас есть классы X, Y, и Z. Пусть у нас есть метод X::M().
2. Теперь мы создаем класс XY и переопределяем для него метод XY::M(). В данный момент принудительный вызов обоих версий метода кажется совершенно излишним — если бы классический программист хотел этого добиться, то он бы просто вставил этот вызов в тело XY::M(), при этом полностью управляя последовательностью вызовов.
3. Теперь мы порождаем класс XYZ и снова переопределяем M(). Предыдущее рассуждение все еще остается справедливым — хорошо смеется тот, кто стреляет последним.
4. А вот теперь кто-то приходит и добавляет в систему класс XZ. С точки зрения классического программиста, это не приносит ничего нового, ибо класс XYZ c ним никак не связан. Наша неклассическая система считает себя умнее программиста, и вызывает X::M(), XY::M(), XZ::M(), XYZ::M() в неопределенном порядке, предоставляя каждой версии доступ на чтение к наиболее свежему кандидату на возвращаемое значение. И об этом неклассический программист просто проинформирован. Нда, я пока не готов стать неклассическим программистом — я бы все же предпочел иметь немного больше контроля над развитием событий.

Все дальнейшие рассуждения в статье без особых оговорок применимы к С++, вплоть до реляционных БД.
Проблемы объединения ООП и РСУБД никак не связаны с трудностями отображения объектов на таблицы. И уж конечно, фиксация единственного способа отображения не является решением подобной проблемы, даже если бы она и была. Трудности начинаются в тот момент, когда нужно выбрать те из объектов класса X, у которых метод M() возвращает 0. Если вспомнить о том, что у нас есть еще классы XY, XZ, и XYZ, то можно сразу выбросить на помойку идею применить индекс к этому запросу — увы, метод M виртуален. И даже для детерминированных методов (от которых мы только что отказались, решив вызывать все подряд) поиск оптимального плана выполнения такого запроса значительно сложнее, чем для типичного RDBMS запроса.
А финальную фразу этого раздела я вообще не понял — как это у нас парадигма привязки произвольного набора классов к каждому объекту умудрилась хорошо просочетаться с теорией баз данных, учитывая выбранный способ отображения? Т.е. у нас теперь один объект — одна табличка? Нда, вряд ли бы корифеи реляционной алгебры это одобрили.

Заключение
Помимо очевидных внутренних нестыковок, я сумел найти единственное радикальное отличие предлагаемой технологии от "классического ООП". Это методика разрешения неопределенностей вызова виртуальных методов при множественном наследовании. Возможно, я ее просто не понял, но на первый взгляд она выглядит значительно хуже любой альтернативы, которую я только могу придумать.
... << RSDN@Home 1.0 beta 6a >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Статья про развитие идей ООП. Жду комментариев.
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.05.03 07:40
Оценка: 3 (1)
Здравствуйте, Voblin, Вы писали:

Да, вдогонку даю ссылку на цикл статей по теории ООП, который мне очень понравился:

The Theory of Classification, Perspectives on Type Compatibility
The Theory of Classification, Part 2: The Scratch-Built Typechecker
The Theory of Classification, Part 3: Object Encoding and Recursion
The Theory of Classification, Part 4: Object Types and Subtyping
The Theory of Classification, Part 5: Axioms, Assertions and Subtyping
The Theory of Classification, Part 6: The Subtyping Inquisition
The Theory of Classification, Part 7: A Class is a Type Family
... << RSDN@Home 1.0 beta 6a >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Статья про развитие идей ООП. Жду комментариев.
От: Voblin Россия http://maslyaew.narod.ru/
Дата: 14.05.03 08:36
Оценка:
Здравствуйте, Akzhan, Вы писали:

A>Здравствуйте, Voblin, Вы писали:


A>http://www.rsdn.ru/Forum/Message.aspx?mid=266637&amp;only=1
Автор: PM
Дата: 14.05.03


Спасибо. Почитаю.
Re[2]: Статья про развитие идей ООП. Жду комментариев.
От: Voblin Россия http://maslyaew.narod.ru/
Дата: 14.05.03 11:26
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Что-то я так и не понял, чем же это отличается от "классического ООП в духе Страуструпа".

S>Ну, вот есть у нас классы. (Все классически). Ну вот может объект принадлежать к нескольким классам — тоже классика. (Вообще говоря, любой ациклический граф соответствует валидной схеме наследования классов).
Видимо Вы имеете в виду множественное наследование. У меня речь о другом.

S>Требование

присутствия возможности задания перечня классов непосредственно для каждого объекта

давно удовлетворено в классике. Никто не мешает писать так:

S>
S>  class _useonceforIbo_ : piblic IGadget, IBark{} *Ibo = new _useonceforIbo_();
S>

S>хотя так делать и не стоит. А не стоит потому, что теперь мы получили объект уникального класса. Легкая модификация:
S>
S>  class _useonceforIbo_ : piblic IGadget, IBark{} *Ibo = new _useonceforIbo_();
S>  class _useonceforIbo2_ : piblic IGadget, IBark{} *IboSon = new _useonceforIbo2_();
S>

S>и упс! У нас два объекта различных классов. Хотя набор интерфейсов в точности совпадает.
У меня классы Ibo и IboSon будут идентичны.

S>Так что на диаграмме надо бы стереть слова "Non-classic".

S>Ага, дальше мы собрались отказаться от наследования и вместо "реального" класса XY: public X, Y {} ввести виртуальный класс XY. Пардон, а чем он так радикально отличается от своего "реального" тезки?
Хорошо. По порядку. Реальный класс должен быть объявлен как положено, и экземпляры мы порождаем уже от него.
Виртуальный же тем и виртуален, что он нигде не объявлен. Мы порождаем объект от двух классов и считаем, что теперь он принадлежит трём классам — двум реальным и одному виртуальному. Для виртуального мы тоже можем определять свойства и методы. В этом случае он станет более реальным, но всё равно у него останется налёт виртуальности: порождая экземпляр классов X,Y,Z получим экземпляр, одновременно принадлежащий классам X,Y,Z,XY,XZ,YZ,XYZ из которых первые три — реальные, последний — под вопросом, а остальные — виртуальные.

S>

S>Вооружившись комбинаторикой, можно вспомнить, что количество сочетаний N классов, выбранных среди K существующих (без учета порядка), равно С(K, N) = K!/(N!*(K-N)!))


Дурака свалял. Конечно, надо считать
S(N=1..K)(K!/(N!*(K-N)!)))
Не знаю, как это сказать по-комбинаторски.

S>Обычно создают родительский класс clPersistent, имеющий методы Read() и Save(), и классы clClient и clOrder порождают (возможно, косвенно) от Persistent.

. . .
S>Запись реквизитов контрагента в базу данных будет производиться в процедуре Save() класса clPersistentClient: public clPersistent, clClient{}, а реквизитов заказа — в процедуре, реализованной для clPersistentOrder: public clPersistent, clOrder{}.
S>Ну да, анси паскаль отдыхает. Впрочем, мы и раньше это знали.
В примере с clPersistent, clClient и clOrder мне нужно было показать, как мы можем добиться полиморфизма без использования наследования. По-моему, мне это удалось.
А то, что ту же логику можно реализовать на С++, я и сам знаю. На Pascalе тоже кстати можно это сделать. Только чуть по-другому.

S>Я не очень понял пассаж про вызов метода класса XZ для объекта класса XY. Вот если бы наш объект был класса XYZ, тогда бы эта кандидатура была бы оправдана.

М.б. я просто неясно выразился, но имелось в виду именно это.

S>Ок, давайте рассмотрим такую ситуацию:

S>1. У нас есть классы X, Y, и Z. Пусть у нас есть метод X::M().
S>2. Теперь мы создаем класс XY и переопределяем для него метод XY::M(). В данный момент принудительный вызов обоих версий метода кажется совершенно излишним — если бы классический программист хотел этого добиться, то он бы просто вставил этот вызов в тело XY::M(), при этом полностью управляя последовательностью вызовов.
S>3. Теперь мы порождаем класс XYZ и снова переопределяем M(). Предыдущее рассуждение все еще остается справедливым — хорошо смеется тот, кто стреляет последним.
S>4. А вот теперь кто-то приходит и добавляет в систему класс XZ. С точки зрения классического программиста, это не приносит ничего нового, ибо класс XYZ c ним никак не связан. Наша неклассическая система считает себя умнее программиста, и вызывает X::M(), XY::M(), XZ::M(), XYZ::M() в неопределенном порядке, предоставляя каждой версии доступ на чтение к наиболее свежему кандидату на возвращаемое значение. И об этом неклассический программист просто проинформирован. Нда, я пока не готов стать неклассическим программистом — я бы все же предпочел иметь немного больше контроля над развитием событий.
Согласен, что надо давать больше контроля. Например так, как это сделано в CLOS — вызов всех подходящих методов производится упорядоченно и программисту даётся возможность повлиять на этот порядок с помощью оператора call_next_method. Сейчас понятно, что здесь есть над чем подумать.

S>Все дальнейшие рассуждения в статье без особых оговорок применимы к С++, вплоть до реляционных БД.

Здесь нужно по порядку:
S>Проблемы объединения ООП и РСУБД никак не связаны с трудностями отображения объектов на таблицы.
Не согласен. Это одна из основных проблем и одна из основных причин появления ООБД.
РСУБД основаны на реляционной алгебре, которая в свою очередь основана на математической теории множеств, а ООП — вообще на чём основано. Когда это пытаешься склеивать, возникают противоречия.

S>И уж конечно, фиксация единственного способа отображения не является решением подобной проблемы, даже если бы она и была.

Готов спорить до хрипоты. В грамотно построенной логически целостной системе должен использоваться только один способ отображения. Всё остальное — кустарщина и дилетантизм. Кроме того, очень много систем автоматически строят структуру БД и структуру классов на базе метаданных. Вот так:
            -- Структура БД
Метаданные <      
            -- Классы

И здесь действительно всякие вольности и шаманство в плане отображения классов на таблицы в принципе невозможно.
Пример — ужасно модная и перспективная MSBS Axapta.

S>Трудности начинаются в тот момент, когда нужно выбрать те из объектов класса X, у которых метод M() возвращает 0. Если вспомнить о том, что у нас есть еще классы XY, XZ, и XYZ, то можно сразу выбросить на помойку идею применить индекс к этому запросу — увы, метод M виртуален. И даже для детерминированных методов (от которых мы только что отказались, решив вызывать все подряд) поиск оптимального плана выполнения такого запроса значительно сложнее, чем для типичного RDBMS запроса.

Очень экзотический случай. Уверен, что ни одна их существующих сейчас РСУБД такое не поддерживает. Может быть, в Yukon что-то такое будет?
Конечно, и сейчас можно в MS SQL 2000 использовать user-defined функции, но это всегда работает очень медленно именно из-за того, что не удаётся оптимизировать план выполнения запроса. Здесь я имею в виду те функции, которые не "Inline Table-valued Functions".

S>А финальную фразу этого раздела я вообще не понял — как это у нас парадигма привязки произвольного набора классов к каждому объекту умудрилась хорошо просочетаться с теорией баз данных, учитывая выбранный способ отображения? Т.е. у нас теперь один объект — одна табличка? Нда, вряд ли бы корифеи реляционной алгебры это одобрили.

Поправлю: один класс — одна табличка. В статье я специально оговорился про подчинённые таблички.

S>Заключение

S>Помимо очевидных внутренних нестыковок, я сумел найти единственное радикальное отличие предлагаемой технологии от "классического ООП". Это методика разрешения неопределенностей вызова виртуальных методов при множественном наследовании. Возможно, я ее просто не понял, но на первый взгляд она выглядит значительно хуже любой альтернативы, которую я только могу придумать.
1. Отказ от наследования — это не отличие?
2. Проблема неопределённости последовательности вызовов решаема. Например так, как я написал выше. Или даже как-нибудь более элегантно.
Re[8]: Шаблоны - не выход. Вернее, не всегда выход.
От: Voblin Россия http://maslyaew.narod.ru/
Дата: 14.05.03 11:41
Оценка:
Здравствуйте, Akzhan, Вы писали:

A>Шаблоны как таковые (при правильном подходе) являются средстством резко уменьшить объём кода. Посмотрите на библиотеки MFC и WTL. При решении одной и той же задачи объём кода разнится на порядки. Причём WTL/ATL использует именно mixin-подход.


Шаблоны — это всего лишь умные макросы. С логической точки зрения это безразлично — использовать template или копирование/вставку+поиск/замену. С макросоми, конечно, продуктивнее. Да и ошибки исправлять проще.

V>2. Мутация бывает очень полезна. Поясняю. Например, пользователь заводит карточку сотрудника. Пол он ещё не ввёл, и поэтому объект не принадлежит ни классу CMale, ни классу CFemale. Выбирает пол сотрудника — "Male". С этого момента неплохо было бы сменить тип редактируемого объекта на (CPerson, CMale).


A>В терминах реляционных баз данных мы просто вносим уточнение на уровне отдельного атрибута. При закачке в оперативную память (swizzling) генерируется объект необходимого типа (всё равно "Персона"). Удобно для случая stateless objects.


A>Если же рассматривать, почему: Вы подобрали плохой пример — здесь удобнее не наследование, а включение.

A>Так, "Персона" имеет необязательное свойство "Пол". Это удобнее, и не надо вводить спорную концепцию.

С полом, конечно, пример не совсем удачный. Хотя всяко может быть. Особенно, если CFemale реализует женскую логику

Более удачный пример — Контрагент — (физ/юр) лицо. Это действительно разные классы, каждый из которых обладает своим набором атрибутов (даже длина поля ИНН разная).
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.