Re[3]: А generic-и: попытка упростить описание
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 08:11
Оценка:
Здравствуйте, Sinclair, Вы писали:

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

E>>Признаю, что в исходном посте я сделал слишком объемное и сложное описание. Приношу свои извинения.
E>>Попробую сделать его попроще.
S>Спасибо. Я правильно понял, что суть проблемы сводится к следующему:
S>1. У нас был универсальный класс нетипизированного сообщения
S>2. У нас был шаблонный класс типизированного сообщения, унаследованный от нетипизированного
S>3. У нас были нешаблонные классы-потомки нетипизированного сообшения

Нет не так.
Был шаблонный класс типизированного сообщения, который ни от кого не наследовался (so_msg_templ_t).
Были нешаблонные типизированные сообщения, которые наследовались от шаблонного типизированного сообщения (т.е. от конкретной инстанции so_msg_templ_t).
Были типизированные сообщения, которые ни от кого не наследовались, но имели конструктор с тремя параметрами.

Именно такие типы указывались в параметре Message_type шаблону so_postman_templ_t. А использовались они для асинхронной доставки агентам объектов, производных от msg_t. Т.е. была своя иерархия классов, производных от msg_t, каждому типу из которой соответствовал какой-то случай из вышеперечисленных.

S>4. У всех классов были конструкторы с тремя параметрами, что использовалось внутри кода доставки для порождения новых сообщений.


Да.

S>Не вполне понятно, как выглядит теперь сигнатура метода Deliver и откуда брать четвертый параметр для сообщений, у которых его нет. Ну да ладно. Попробуем так угадать.


Да все элементарно (хотя сомневаюсь, что это красиво с позиций архитектурной красоты). У базового класса postman_t изначально был один виртуальный метод:
virtual void
deliver(
    const mbapi_3::dest_t & to,
    const mbapi_3::msg_t & msg,
    const mbapi_3::dest_t & reply_to,
    unsigned int delay ) = 0;


Затем был добавлен еще один метод:
virtual void
deliver( const delivery_info_t & info );


Который просто вызывал метод deliver(to, msg, reply_to, info). В коде route теперь вызвается deliver(info). Это позволило обеспечить совместимость с кодом, который не использовал so_postman_templ_t, а создавал своих почтальнов, непосредственно наследуясь от postman_t.
Сам же so_postman_templ_t теперь выполняет свои действия именно в deliver(info).

Спасибо за пример. Про применимость reflection я знал заранее, но ведь это не строготипизированное решение. Думал, что можно без reflection обойтись.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[4]: А generic-и: попытка упростить описание
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 08:46
Оценка:
Два исправления:

E>Который просто вызывал метод deliver(to, msg, reply_to, delay). В коде route теперь вызвается deliver(info). Это позволило обеспечить совместимость с кодом, который не использовал so_postman_templ_t, а создавал своих почтальнов, непосредственно наследуясь от postman_t.


E>Спасибо за пример. Про применимость reflection я знал заранее, но ведь это не статически типизированное решение. Думал, что можно без reflection обойтись.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[4]: А generic-и: попытка упростить описание
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.06.05 10:41
Оценка: 37 (2)
Здравствуйте, eao197, Вы писали:
E>Нет не так.
E>Был шаблонный класс типизированного сообщения, который ни от кого не наследовался (so_msg_templ_t).

E>Были нешаблонные типизированные сообщения, которые наследовались от шаблонного типизированного сообщения (т.е. от конкретной инстанции so_msg_templ_t).

E>Были типизированные сообщения, которые ни от кого не наследовались, но имели конструктор с тремя параметрами.
Ок, попробуем по другому. Поскольку с самого начала нам надо порождать MessageType, то мы пойдем по компонентному пути:
public abstract class Postman
{
  // handles the delivery of the message
  public virtual void deliver(Dest to, Msg msg, Dest replyTo);
}

Это основа нашего постмена.
У нас будет обобщенный класс сообщения, устроенный как грабли:
public class GenericMessage<ToType, MsgType, ReplyToType>
    where ToType: Dest
    where MsgType: Msg
    where ReplyToType: Dest;
{

    public GenericMessage(SomeDestination to, Msg msg, SomeOtherDestination)
    {
      ...
    }
}

Поскольку дженерики не позволяют применить ограничения на конструктор с параметрами, воспользуемся фабрикой:
public delegate MessageType MessageFactory<MessageType, ToType, MsgType, ReplyToType>(ToType to, MsgType msg, ReplyToType replyTo)
    where ToType: Dest
    where MsgType: Msg
    where ReplyToType: Dest;

Соответственно, в класс почтальона мы передадим экземпляр порождающего делегата:
public class Postman<MessageType, ToType, MsgType, ReplyToType>: Postman
    where ToType: Dest
    where MsgType: Msg
    where ReplyToType: Dest;
{
    private MessageFactory<MessageType, ToType, MsgType, ReplyToType> _factory;
    private Queue<MessageType> _queue = new Queue<MessageType>();

    public Postman(MessageFactory<MessageType, ToType, MsgType, ReplyToType> factory)
    {
        _factory = factory;
    }
    
    override void deliver(Dest to, Msg msg, Dest replyTo)
    {
        deliver((ToType)to, (MsgType)msg, (ReplyToType)replyTo);
    }
    public virtual void deliver(ToType to, MsgType msg, ReplyToType replyTo)
    {
        _queue.Enqueue(_factory(to, msg, replyTo));
    }
}

Сделаем также почтальона по доставке обобщенных сообщений:
public class GenericPostman<ToType, MsgType, ReplyToType>: Postman<GenericMessage<ToType, MsgType, ReplyToType>, ToType, MsgType, ReplyToType>
{
  // Factory helper
  public static GenericMessage<ToType, MsgType, ReplyToType> CreateMessage(ToType to, MsgType msg, ReplyToType replyTo)
    {
        return new GenericMessage<ToType, MsgType, ReplyToType>(to, msg, replyTo);
    }
    
    public GenericPostman(): base (CreateMessage)
    {
    }
}


Для нетривиальных случаев конструктор нам придется вызывать руками:
public class SpecificMessageType
{
  public SpecificMessageType(SomeDestination to, SomeMessageType msg, SomeOtherDestination replyTo)
    {
    ...
    }
}
{
  Router.RegisterPostman(new GenericPostman<SomeDestination, SomeMessageType, SomeOtherDestination>()); // это простой случай
    
  Router.RegisterPostman(new Postman<SpecificMessageType, SomeDestination, SomeMessageType, SomeOtherDestination>
        ( new delegate(SomeDestination to, SomeMessageType msg, SomeOtherDestination replyTo)
            {    return new SpecificMessageType(to, msg, replyTo) }
        )
    );
}


Итак, все готово.
А теперь попробуем дополнить конструкторы сообщений четвертым параметром.
Вот дополнительный код:

public class GenericMessage<ToType, MsgType, ReplyToType, ExtendedDataType>
    where ToType: Dest
    where MsgType: Msg
    where ReplyToType: Dest;
{

    public GenericMessage(SomeDestination to, Msg msg, SomeOtherDestination to, ExtendedDataType ext)
    {
      ...
    }
}

public delegate MessageType MessageFactory<MessageType, ToType, MsgType, ReplyToType, ExtendedDataType>(ToType to, MsgType msg, ReplyToType replyTo, ExtendedDataType ext)
    where ToType: Dest
    where MsgType: Msg
    where ReplyToType: Dest;


Теперь нам понадобится новый почтальон, умеющий работать с новыми параметрами:
public class Postman<MessageType, ToType, MsgType, ReplyToType, ExtendedDataType>: Postman
    where ToType: Dest
    where MsgType: Msg
    where ReplyToType: Dest;
{
    private MessageFactory<MessageType, ToType, MsgType, ReplyToType, ExtendedDataType> _factory;
    private Queue<MessageType> _queue = new Queue<MessageType>();

    public Postman(MessageFactory<MessageType, ToType, MsgType, ReplyToType, ExtendedDataType> factory)
    {
        _factory = factory;
    }
    
    override void deliver(DeliveryInfo info)
    {
        _queue.Enqueue(_factory(info.to, info.msg, info.replyTo, info.extendedData));
    }
}

новый GenericPostman мы получим тем же способом. Также надо будет поменять базового Postman также, как у тебя. Чтобы все четыре его наследника работали корректно.
Вроде вот так.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: А generic-и: попытка упростить описание
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 11:01
Оценка:
Здравствуйте, Sinclair

Спасибо. Интересно.
Но, как и в C++, не слишком компактно.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[5]: А generic-и: попытка упростить описание
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 11:12
Оценка:
Здравствуйте, Sinclair


Проясни мне одну вещь. Вот был у меня GenericPostman от трех параметров. И использовал я его так, как ты показал:
Router.RegisterPostman(new GenericPostman<SomeDestination, SomeMessageType, SomeOtherDestination>()); // это простой случай


А затем у нас появляется GenericPostman с четырьмя шаблонными параметрами. Останется ли приведенная выше запись неизменной или мне придется переписывать:
Router.RegisterPostman(
    new GenericPostman<
            SomeDestination, SomeMessageType, SomeOtherDestination, NoExtendedInfo >()); // это простой случай


Если придется переписывать, то я так понимаю, мне потребуется оставить GenericPostman как есть и создать ExtendedGenericPostman, который будет уже зависеть от четырех шаблонных параметров?

Кстати, в C#-generic-ах может ли быть параметром шаблона скаляр (как в моем случае true/false)?
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[6]: А generic-и: попытка упростить описание
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.06.05 11:22
Оценка:
Здравствуйте, eao197, Вы писали:
E>Спасибо. Интересно.
E>Но, как и в C++, не слишком компактно.
На самом деле проблема именно в том, что не хватает возможности ограничивать сигнатуру конструктора.

Да, в реальном приложении код мог бы быть значительно более компактным. Я просто не очень хорошо себе представляю задачу — зело велика. Если бы приложение с самого начала проектировалось под С#, то в нем возможно вообще бы были применены другие средства. Я попытался написать 1:1 отображение исходной архитектуры + уродливые костыли для замены отсутствующей функциональности.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: А generic-и: попытка упростить описание
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.06.05 11:42
Оценка:
Здравствуйте, eao197, Вы писали:
E>Проясни мне одну вещь. Вот был у меня GenericPostman от трех параметров. И использовал я его так, как ты показал:
E>
E>Router.RegisterPostman(new GenericPostman<SomeDestination, SomeMessageType, SomeOtherDestination>()); // это простой случай
E>

Угу.
E>А затем у нас появляется GenericPostman с четырьмя шаблонными параметрами. Останется ли приведенная выше запись неизменной или мне придется переписывать:
Останется. Все в порядке. Дженерики с разным числом аргументов — разные дженерики.
E>Кстати, в C#-generic-ах может ли быть параметром шаблона скаляр (как в моем случае true/false)?
Увы, нет. Позволено только типы.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: А generic-и: попытка упростить описание
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 11:42
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Да, в реальном приложении код мог бы быть значительно более компактным. Я просто не очень хорошо себе представляю задачу — зело велика.


Вероятно, я не могу ее сжато сформулировать потому, что это только небольшая часть небольшой библиотеки для обмена сообщениями.

S> Если бы приложение с самого начала проектировалось под С#, то в нем возможно вообще бы были применены другие средства.


Так это понятно. Мне просто интересно было, можно ли трюки с C++ шаблонами применять в C#/Java generic-ах.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[7]: А generic-и: попытка упростить описание
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 12:09
Оценка:
Здравствуйте, Sinclair, Вы писали:

E>>А затем у нас появляется GenericPostman с четырьмя шаблонными параметрами. Останется ли приведенная выше запись неизменной или мне придется переписывать:

S>Останется. Все в порядке. Дженерики с разным числом аргументов — разные дженерики.

Но тогда получается, что у меня есть две параллельные системы классов и фабрик:
GenericMessage<To,Msg,ReplyTo>
MessageFactory<MessageType,To,Msg,ReplyTo>
Postman<MessageType,To,Msg,ReplyTo>
GenericPostman<To,Msg,ReplyTo>

и
GenericMessage<To,Msg,ReplyTo,Extended>
MessageFactory<MessageType,To,Msg,ReplyTo,Extended>
Postman<MessageType,To,Msg,ReplyTo,Extended>
GenericPostman<To,Msg,ReplyTo,Extended>


Причем их основной код будет одним и тем же. И если мне потребуется что-то исправить или доделать, то делать это нужно будет в каждой из систем независимо друг от друга (естественно, попытавшись что-то вынести в общий базовый класс)?
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[8]: А generic-и: попытка упростить описание
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.06.05 20:13
Оценка: +1
Здравствуйте, eao197, Вы писали:

E>Но тогда получается, что у меня есть две параллельные системы классов и фабрик:

E>
E>GenericMessage<To,Msg,ReplyTo>
E>MessageFactory<MessageType,To,Msg,ReplyTo>
E>Postman<MessageType,To,Msg,ReplyTo>
E>GenericPostman<To,Msg,ReplyTo>
E>

E>и
E>
E>GenericMessage<To,Msg,ReplyTo,Extended>
E>MessageFactory<MessageType,To,Msg,ReplyTo,Extended>
E>Postman<MessageType,To,Msg,ReplyTo,Extended>
E>GenericPostman<To,Msg,ReplyTo,Extended>
E>

Верно.
E>Причем их основной код будет одним и тем же.
Нет.
E> И если мне потребуется что-то исправить или доделать, то делать это нужно будет в каждой из систем независимо друг от друга (естественно, попытавшись что-то вынести в общий базовый класс)?
Вряд ли. Основная часть кода, который спрятан в этих классах — оберточная. Там практически нечего исправлять. Скорее тебе захочется ввести еще пару параллельных классов.
Еще раз намекаю, что решение было получено из прямого переноса плюсовых практик. Системы доставки сообщений на C# проектируются не так.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: А generic-и: а еще дальше?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 20:52
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>
S>  Router.RegisterPostman(new GenericPostman<SomeDestination, SomeMessageType, SomeOtherDestination>()); // это простой случай
    
S>  Router.RegisterPostman(new Postman<SpecificMessageType, SomeDestination, SomeMessageType, SomeOtherDestination>
S>        ( new delegate(SomeDestination to, SomeMessageType msg, SomeOtherDestination replyTo)
S>            {    return new SpecificMessageType(to, msg, replyTo) }
S>        )
S>    );
S>}
S>


Хочу задать ламерский вопрос, но я не знаю, есть ли в C# понятие typedef или alias, чтобы можно было дать псевдоним сложной конструкции.
Вот Sinclair показал, как с помощью generic-ов можно регистрировать почтальонов с привязкой к конкретным типам. Но, сигнатура GenericPostman-а в программе будет дублироваться: один раз, как показано выше, при регистрации почтальона. А второй раз при описании обработчика сообщения в агенте:
class    SomeAgent
{
    public void receiveSomeMessage( GenericMessage< SomeDestination, SomeMessageType, SomeOtherDestination > msg )
        { ... }
};


Имхо, такое дублирование не есть good. В C++ я избавился от него при помощи двух простых трюков. Во-первых, в шаблоне сообщения просто сделал typedef-ы:
template< class Message, class To, class ReplyTo, bool Extended_info_required = false >
struct so_msg_templ_t
    {
        typedef Message    message_type_t;
        typedef To    to_type_t;
        typedef ReplyTo    reply_to_type_t;
        enum { extended_info_required = Extended_info_required };
        ...
    };


Во-вторых, сделал шаблонный класс почтальона, который зависит только от одного шаблонного параметра:
template< class So_message >
class    so_msg_templ_postman_t
    :    public postman_t
    {
        // Агрегируем функциональность ранее созданного шаблона so_postman_templ_t.
        typedef so_postman_templ_t<
                        // Вот здесь берем псевдонимы из So_message.
                        typename So_message::message_type_t,
                        So_message,
                        typename So_message::to_type_t,
                        typename So_message::reply_to_type_t,
                        So_message::extended_info_required >
                real_postman_t;
                
        real_postman_t    m_postman;
        ...
    };


Что позволило мне указывать список шаблонных параметров всего в одном месте программы:
typedef so_msg_templ_t< some_message_t, some_dest_t, some_another_dest_t > my_message_t;

so_msg_templ_postman_t< my_message_t > my_postman;

void
some_agent_t::evt_receive_my_message( const my_message_t & msg ) {...}



А вот такие фокусы с generic-ами возможны?
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[6]: А generic-и: а еще дальше?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 25.06.05 20:57
Оценка: 18 (1)
Здравствуйте, eao197, Вы писали:

E>А вот такие фокусы с generic-ами возможны?


Нет. Дженерики принципиально не годятся для кодогенерации, поскольку обязаны быть компилируемыми.
... << RSDN@Home 1.2.0 alpha rev. 500>>
AVK Blog
Re[7]: А generic-и: а еще дальше?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 25.06.05 21:02
Оценка:
Здравствуйте, AndrewVK, Вы писали:

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


E>>А вот такие фокусы с generic-ами возможны?


AVK>Нет. Дженерики принципиально не годятся для кодогенерации, поскольку обязаны быть компилируемыми.


Понятно, только какая же это кодогенерация Это так, ничего особенного
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[6]: А generic-и: а еще дальше?
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.06.05 21:14
Оценка:
Здравствуйте, eao197, Вы писали:

E>Хочу задать ламерский вопрос, но я не знаю, есть ли в C# понятие typedef или alias, чтобы можно было дать псевдоним сложной конструкции.

И да и нет.
Да — потому, что можно написать
using IntList = System.Collections.Generic.List<int>;

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

E>Вот Sinclair показал, как с помощью generic-ов можно регистрировать почтальонов с привязкой к конкретным типам. Но, сигнатура GenericPostman-а в программе будет дублироваться: один раз, как показано выше, при регистрации почтальона. А второй раз при описании обработчика сообщения в агенте:

E>А вот такие фокусы с generic-ами возможны?
Напрямую — нет. Но, имхо, если пошевелить мозгой, то можно получить как минимум для данного конкретного случая корректный результат...
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: А generic-и: попытка упростить описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 26.06.05 01:39
Оценка:
Здравствуйте, Sinclair, Вы писали:

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

E>>Признаю, что в исходном посте я сделал слишком объемное и сложное описание. Приношу свои извинения.
E>>Попробую сделать его попроще.
S>Спасибо. Я правильно понял, что суть проблемы сводится к следующему:
S>1. У нас был универсальный класс нетипизированного сообщения
S>2. У нас был шаблонный класс типизированного сообщения, унаследованный от нетипизированного
S>3. У нас были нешаблонные классы-потомки нетипизированного сообшения
S>4. У всех классов были конструкторы с тремя параметрами, что использовалось внутри кода доставки для порождения новых сообщений.

Незнаю насколько ты угадал. Мне просто влом вникать даже в уменьшенный вариант. Если ты угодал, то можно обойтись и без рефлекшона. Нужно просто ввести делегат заменяющий конструктор:
delegate Result Creator<Result, T1, T2, T3>(T1 t1, T2 t2, T3 t3);

class Destination { }
class Msg { }
class Message
{
    public Message(Destination to, Msg msd, Destination replyTo)  {  }
}

class Postman<MessageType>
{
    public Postman(Creator<MessageType, Destination, Msg, Destination> creator) { _creator = creator; }

    Creator<MessageType, Destination, Msg, Destination> _creator;
 
    public void Deliver(Destination to, Msg msd, Destination replyTo)
    {
        MessageType message = _creator(to, msd, replyTo);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Postman<Message> postman = new Postman<Message>(
            delegate(Destination to, Msg msd, Destination replyTo) { return new Message(to, msd, replyTo); });

        postman.Deliver(new Destination(), new Msg(), new Destination());
    }
}



S>Поэтому на практике, для сообщений скорее всего будет применяться фабрика. И приплясываний с бубном не потребуется вообще.


Вот именно.

ЗЫ

Кстати такие навороты с рефлекшоном для динамического создания объектов не нужны. Нужно использовать Activator. Примерно так:
class Postman<MessageType>
{
    public void Deliver(Destination to, Msg msd, Destination replyTo)
    {
        MessageType message = (MessageType)Activator.CreateInstance(typeof(MessageType), new object[] { to, msd, replyTo });
    }
}
... << RSDN@Home 1.1.4 beta 7 rev. 466>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.