Уважаемые коллеги!
Одним из самых полезных для меня качеств языка 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>>