Здравствуйте, 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 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++.
Два исправления:
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++.
Здравствуйте, eao197, Вы писали: E>Нет не так. E>Был шаблонный класс типизированного сообщения, который ни от кого не наследовался (so_msg_templ_t).
E>Были нешаблонные типизированные сообщения, которые наследовались от шаблонного типизированного сообщения (т.е. от конкретной инстанции so_msg_templ_t). E>Были типизированные сообщения, которые ни от кого не наследовались, но имели конструктор с тремя параметрами.
Ок, попробуем по другому. Поскольку с самого начала нам надо порождать MessageType, то мы пойдем по компонентному пути:
public abstract class Postman
{
// handles the delivery of the messagepublic 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 helperpublic 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>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Проясни мне одну вещь. Вот был у меня 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++.
Здравствуйте, eao197, Вы писали: E>Спасибо. Интересно. E>Но, как и в C++, не слишком компактно.
На самом деле проблема именно в том, что не хватает возможности ограничивать сигнатуру конструктора.
Да, в реальном приложении код мог бы быть значительно более компактным. Я просто не очень хорошо себе представляю задачу — зело велика. Если бы приложение с самого начала проектировалось под С#, то в нем возможно вообще бы были применены другие средства. Я попытался написать 1:1 отображение исходной архитектуры + уродливые костыли для замены отсутствующей функциональности.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, 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>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Да, в реальном приложении код мог бы быть значительно более компактным. Я просто не очень хорошо себе представляю задачу — зело велика.
Вероятно, я не могу ее сжато сформулировать потому, что это только небольшая часть небольшой библиотеки для обмена сообщениями.
S> Если бы приложение с самого начала проектировалось под С#, то в нем возможно вообще бы были применены другие средства.
Так это понятно. Мне просто интересно было, можно ли трюки с C++ шаблонами применять в C#/Java generic-ах.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Sinclair, Вы писали:
E>>А затем у нас появляется GenericPostman с четырьмя шаблонными параметрами. Останется ли приведенная выше запись неизменной или мне придется переписывать: S>Останется. Все в порядке. Дженерики с разным числом аргументов — разные дженерики.
Но тогда получается, что у меня есть две параллельные системы классов и фабрик:
Причем их основной код будет одним и тем же. И если мне потребуется что-то исправить или доделать, то делать это нужно будет в каждой из систем независимо друг от друга (естественно, попытавшись что-то вынести в общий базовый класс)?
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Верно. E>Причем их основной код будет одним и тем же.
Нет. E> И если мне потребуется что-то исправить или доделать, то делать это нужно будет в каждой из систем независимо друг от друга (естественно, попытавшись что-то вынести в общий базовый класс)?
Вряд ли. Основная часть кода, который спрятан в этих классах — оберточная. Там практически нечего исправлять. Скорее тебе захочется ввести еще пару параллельных классов.
Еще раз намекаю, что решение было получено из прямого переноса плюсовых практик. Системы доставки сообщений на C# проектируются не так.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
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;
...
};
Что позволило мне указывать список шаблонных параметров всего в одном месте программы:
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, eao197, Вы писали:
E>>А вот такие фокусы с generic-ами возможны?
AVK>Нет. Дженерики принципиально не годятся для кодогенерации, поскольку обязаны быть компилируемыми.
Понятно, только какая же это кодогенерация Это так, ничего особенного
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, 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>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, 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>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.