А generic-и так могут?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 29.05.05 20:25
Оценка: 5 (1)
Уважаемые коллеги!

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

Не хотелось бы превращать эту тему в очередной холивар по поводу C++ vs C# vs Java. Просто я на днях с помощью шаблонов расширил функциональность одного из модулей. При этом не потребовалась модификация кода, который работал с этим модулем ранее. Ниже я привожу примененное мной решение на C++.

Я хотел бы узнать, можно ли подобные вещи вытворять с помощью generic-ов в C# и/или Java. И если можно, насколько это выглядит проще/читабельнее и/или эффективнее.



Задача в том, что есть два механизма доставки получателям сообщений. Один синхронный (под названием mbapi), второй -- асинхронный (под названием SObjectizer). Был создан модуль, позволяющий строить преобразования синхронных сообщений mbapi в асинхронные сообщения SObjectizer-а. Для этого, каждому сообщению mbapi нужно было сопоставить специальный объект-почтальон (postman), который преобразовывает экземпляр асинхронного mbapi-сообщения, в экземпляр асинхронного сообщения и производит отсылку SObjectizer-сообщения получателю.

Для удобства использования всей этой кухни было сделано несколько вспомогательных шаблонных класса. Один из них, so_postman_templ_t был базовым шаблоном для почтальона. Он требовал, чтобы его параметризовали типом сообщения, у которого есть конструктор с тремя аргументами:
Sobj_type(
    // Адрес получателя сообщения.
    To_type * to,
    // Само сообщение.
    Mbapi_type * msg,
    // Адрес отправителя сообщения (обратный адрес).
    Reply_to_type * reply_to );


За время работы с so_postman_templ_t было написано много кода. Часть сообщений, которыми параметризовался so_postman_templ_t были созданы с использованием другого шаблона (so_msg_templ_t -- специальный шаблон для генерации SObjectizer-сообщений из mbapi-сообщений, so_msg_templ_t так же был подобным прозрачным образом модифицирован), но была и часть, которая была специально создана в расчете на первоначальную спецификацию.

Важно отметить, что сперва требовалось оперировать всего тремя параметрами сообщения: адресами отправителя и получателя, и самим сообщением. Но в один прекрасный момент, когда по старой схеме работало уже много чего, возникла необходимость сопоставить сообщению дополнительную, расширенную информацию. Т.е., потребовалось поддержать SObjectizer-сообщения, конструктор которых имеет вид:
Sobj_type(
    To_type * to,
    Mbapi_type * msg,
    Reply_to_type * reply_to,
    std::auto_ptr< mbapi_3::extended_info_t > extended_info );

т.е., в зависимости от того, какие параметры шаблона использованы для инстанцирования so_postman_templ_t, код so_postman_templ_t должен создавать объекты типа Sobj_type используя либо конструктор с тремя параметрами, либо конструктор с четырьмя параметрами. Причем, в коде so_postman_templ_t нельзя было написать простой if и использовать в разных его ветках разные вызовы конструкторов. Ведь тогда компилятор и линкер требовали бы наличия у типа Sobj_type сразу обоих конструкторов. А вся задача состояла в том, чтобы новая версия so_postman_templ_t умела работать со старыми типами, у которых конструкторы всего с тремя параметрами.

У меня на C++ шаблонах получилось приведенное ниже решение. Хочу сделать два отступления. Во-первых, требовалось обеспечить совместимость с VC++ 6.0, в котором нет поддержки частичной специализации. Из-за этого пришлось ввести дополнительные типы: sender_params_t, no_extended_info_support_sender_t, extended_info_support_sender_t, sender_selector_t. Если бы можно было применить частную специализацию, то можно было обойтись всего одним типом и его частичной специализацией. И решение бы сильно сократилось в объеме.
Во-вторых, я понимаю, что ошибка была допущена в самом начале, когда было решено передавать в конструктор Sobj_type всю имеющуюся информацию в виде отдельных параметров. Проблем бы не было, если бы передавался всего один аргумент -- некая структура, из которой можно было бы извлечь все. Да, такая ошибка была мной допущена. Но, она была выявлена после двух лет успешной эксплуатации, когда начали меняться требования к имеющемуся механизму. И вот в таких неприятных, но реальных, условиях шаблоны C++ позволили обеспечить работоспособность уже написанного кода.

Вот, собственно, само решение:
//
// extended_info_t
//

/*!
    \since v.3.3.0
    \brief Расширеная информация об MBAPI-сообщении, которая доступна
        для получателя MBAPI-сообщения.

    Большинству получателей MBAPI-сообщений необходимо всего три значения:
    пункт назначения сообщения, само сообщение и его обратный адрес.
    Но в версии 3.3.0 получателю предоставляется так же возможность получить
    историю ремаршрутизаций сообщения. В следующих версиях получатель может
    захотеть получать еще какую-то информацию о сообщении. Для того, чтобы
    не модифицировать для этого шаблон so_templ_postman_t предлагается
    всю дополнительную информацию передавать получателю в виде структуры
    extended_info_t, которая будет расширятся дополнительными атрибутами по
    мере надобности.
*/
struct    extended_info_t
    {
        //! История ремаршрутизации сообщений.
        std::auto_ptr< rerouting_history_t >    m_rerouting_history;

        //! Инициализирующий конструктор.
        /*!
            Самостоятельно извлекает всю информацию из delivery_info_t.
        */
        extended_info_t( const delivery_info_t & info )
            :    m_rerouting_history( info.rerouting_history() ?
                    new rerouting_history_t( *info.rerouting_history() ) : 0 )
            {}
        ~extended_info_t()
            {}
        
        /*! \name Методы getter-ы.
            \{ */
        const rerouting_history_t *
        rerouting_history() const { return m_rerouting_history.get(); }
        /*! \} */
    };

namespace impl
{

//
// sender_params_t
//

/*!
    \since v.3.3.0
    \brief Вспомогательная обертка для параметров доставки MBAPI-сообщения
        в виде SObjectizer сообщения.

    Необходима, главным образом, для того, чтобы сделать вспомогательные
    классы no_extended_info_support_sender_t и extended_info_support_sender_t
    не шаблонными -- это необходимо для поддержки C++ компилятров, в которых
    нет частичной специализации шаблонов.
*/
template<
        class Mbapi_type,
        class Sobj_type,
        class To_type,    
        class Reply_to_type >
struct    sender_params_t
    {
        /*! \name Псевдонимы для параметров шаблона.
            \{ */
        typedef Mbapi_type    mbapi_type_t;
        typedef Sobj_type    sobj_type_t;
        typedef To_type    to_type_t;
        typedef Reply_to_type    reply_to_type_t;
        /*! \} */

        //! Исходные параметры MBAPI-сообщения.
        const delivery_info_t &    m_info;

        //! Инициализирующий конструктор.
        sender_params_t( const delivery_info_t & info )
            :    m_info( info )
            {}

        //! Доступ к параметрам MBAPI-сообщения.
        const delivery_info_t &
        info() const { return m_info; }
    };

//
// no_extended_info_support_sender_t
//

/*!
    \since v.3.3.0
    \brief Генератор SObjectizer-сообщения, для случая, когда конструктор
        сообщения получает всего три параметра.
*/
struct    no_extended_info_support_sender_t
    {
        //! Шаблонный метод для генерации SObjectizer-сообщения.
        /*!
            \note В качестве параметра шаблона ожидается sender_params_t.
        */
        template< class Params >
        void
        send(
            //! Имя владельца сообщения.
            const std::string & owner_name,
            //! Имя SObjectizer-сообщения.
            const std::string & msg_name,
            //! Все доступные параметры MBAPI-сообщения.
            const Params & p ) const
            {
                // Для защиты от исключений при выделении памяти.
                std::auto_ptr< Params::to_type_t > to(
                        dynamic_cast< typename Params::to_type_t * >(
                                p.info().to().clone() ) );
                std::auto_ptr< Params::mbapi_type_t > msg(
                        dynamic_cast< typename Params::mbapi_type_t * >(
                                p.info().msg().clone() ) );
                std::auto_ptr< Params::reply_to_type_t > reply_to(
                        dynamic_cast< typename Params::reply_to_type_t * >(
                                p.info().reply_to().clone() ) );

                so_4::api::send_msg_safely( owner_name, msg_name,
                        new typename Params::sobj_type_t(
                                to.release(),
                                msg.release(),
                                reply_to.release() ),
                        "", p.info().delay() );
            }
    };

//
// extended_info_support_sender_t
//

/*!
    \since v.3.3.0
    \brief Генератор SObjectizer-сообщения, для случая, когда конструктор
        сообщения получает четвертым параметром rerouting_history.
*/
struct    extended_info_support_sender_t
    {
        //! Шаблонный метод для генерации SObjectizer-сообщения.
        /*!
            \note В качестве параметра шаблона ожидается sender_params_t.
        */
        template< class Params >
        void
        send(
            //! Имя владельца сообщения.
            const std::string & owner_name,
            //! Имя SObjectizer-сообщения.
            const std::string & msg_name,
            //! Все доступные параметры MBAPI-сообщения.
            const Params & p ) const
            {
                // Для защиты от исключений при выделении памяти.
                std::auto_ptr< Params::to_type_t > to(
                        dynamic_cast< typename Params::to_type_t * >(
                                p.info().to().clone() ) );
                std::auto_ptr< Params::mbapi_type_t > msg(
                        dynamic_cast< typename Params::mbapi_type_t * >(
                                p.info().msg().clone() ) );
                std::auto_ptr< Params::reply_to_type_t > reply_to(
                        dynamic_cast< typename Params::reply_to_type_t * >(
                                p.info().reply_to().clone() ) );
                std::auto_ptr< extended_info_t > extended(
                        new extended_info_t( p.info() ) );

                so_4::api::send_msg_safely( owner_name, msg_name,
                        new typename Params::sobj_type_t(
                                to, msg, reply_to, extended ),
                        "", p.info().delay() );
            }
    };

//
// sender_selector_t
//

/*!
    \since v.3.3.0
    \brief Вспомогательный класс для выбора одного из sender-ов.

    Данная версия одновременно является специализацией для случая,
    когда Extended_info_supported == false.
*/
template< bool Extended_info_supported >
struct    sender_selector_t
    {
        typedef no_extended_info_support_sender_t    sender_type_t;
    };

/*!
    \since v.3.3.0

    Специализация sender_selector_t для случая, когда
    Extended_info_suppored == true.
*/
template<>
struct    sender_selector_t< true >
    {
        typedef extended_info_support_sender_t    sender_type_t;
    };

} /* namespace impl */

//
// so_postman_templ_t
//

//! Реализация интерфейса %postman_t в виде шаблона.
/*!

    \par Версии, предшествующие 3.3.0
    Предназначен для генерации SObjectizer-сообщений,
    которые имеют конструктор следующего вида:
\code
Sobj_type(
    To_type * to,
    Mbapi_type * msg,
    Reply_to_type * reply_to );
\endcode

    Например:
\code
struct    msg_receiver_ack_t
{
    std::auto_ptr< mbapi_3::dest_t > m_to;
    std::auto_ptr< mbapi_3::sms::receive_ack_t > m_msg;
    std::auto_ptr< mbapi_3::dest_t > m_reply_to;

    msg_receiver_ack_t() {}
    msg_receiver_ack_t(
        mbapi_3::dest_t * to,
        mbapi_3::sms::receive_ack_t * msg,
        mbapi_3::dest_t * reply_to )
        :    m_to( to )
        ,    m_msg( msg )
        ,    m_reply_to( reply_to )
        {
        }
};

so_postman_templ_t<
        mbapi_3::sms::receive_ack_t,
        msg_receive_ack_t >
    receive_ack_postman(
        "some_agent_name",
        "msg_receive_ack" );
\endcode

    \par Начиная с версии 3.3.0 поддерживаются SObjectizer-сообщения,
    конструктор который имеет следующий вид:
\code
Sobj_type(
    std::auto_ptr< To_type > to,
    std::auto_ptr< Mbapi_type > msg,
    std::auto_ptr< Reply_to_type > reply_to,
    std::auto_ptr< mbapi_3::extended_info_t > extended_info );
\endcode
*/
template<
    // Тип MBAPI-сообщения.
    class    Mbapi_type,
    // Тип SObjectizer-сообщения.
    class    Sobj_type,
    // Тип адреса получателя сообщения.
    class To_type = mbapi_3::dest_t,
    // Тип адреса отправителя сообщения.
    class    Reply_to_type = mbapi_3::dest_t,
    // Поддерживается ли получателем расширенная информация?
    bool Extended_info_supported = false >
class    so_postman_templ_t :
    public postman_t
    {
    private :
        //! Имя агента-владельца сообщения.
        const std::string    m_owner_agent;
        //! Имя сообщения, которое нужно отослать.
        const std::string    m_msg_name;

        //! Объект, который реально будет осущетвлять доставку сообщения.
        const typename impl::sender_selector_t<
                        Extended_info_supported >::sender_type_t
                m_sender;

    public :
        inline
        so_postman_templ_t(
            //! Имя агента-владельца сообщения.
            const std::string & owner_agent,
            //! Имя сообщения, которое нужно отослать.
            const std::string & msg_name )
            :    m_owner_agent( owner_agent )
            ,    m_msg_name( msg_name )
            {}

        virtual ~so_postman_templ_t()
            {}

        virtual void
        deliver(
            const mbapi_3::router::delivery_info_t & info )
            {
                impl::sender_params_t<
                                Mbapi_type, Sobj_type, To_type, Reply_to_type >
                        params( info );
                m_sender.send( m_owner_agent, m_msg_name, params );
            }
    };

} /* namespace router */

} /* namespace mbapi_3 */


Т.е., фокус в том, что если Extended_info_supported равен false (по умолчанию), то so_postman_templ_t::m_sender имеет тип impl::no_extended_info_support_sender_t и для инстанцирования SObjectizer-сообщения используется конструктор с тремя параметрами. В противном случае m_sender имеет тип impl::extended_info_support_sender_t и для инстанцирования SObjectizer сообщения используется контруктор с четырьмя параметрами. Причем осуществляется все это в compile-time.

Получается, что в старом коде и в том новом коде, в котором не требуется extended_info_t, шаблон so_postman_templ_t применяется без последного параметра Extended_info_supported (принимается значение false). Если же новому коду нужно использовать extended_info_t, то при инстанцировании so_postman_templ_t указывается true в качестве параметра Extended_info_supported.

Важным достоинством шаблонов C++ здесь является то, что если extended_info_t получателю сообщения не нужен, то при генерации SObjectizer сообщения он даже не создается.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.