eao197 on the Web Сайт Евгения Охотникова |
[ Главная | Проекты | Описания | Об авторе | Лицензия ] |
C++ tricks: no_copy_paste
При реализации серверной части SMPP протокола возникла необходимость обработки трех вариаций команд BIND: BIND_TRANSMITTER, BIND_RECEIVER, BIND_TRANSCEIVER. Структуры каждой из команд совпадают, да и обработка команд делается аналогично. Только в процессе обработки нужно использовать разные классы, функции и строковые значения.
Сначала я сделал обработку команды BIND_TRANSMITTER без шаблонов, чтобы затем растиражировать получившийся код. Вот что получилось:
void a_channel_t::on_bind_transmitter( const smpp_pdu_1::pdu_t & what ) { smpp_pdu_1::bind_transmitter_t parsed( what ); log_parsed_pdu( parsed ); smpp_pdu_1::pdu_t resp_pdu; smpp_pdu_1::command_header_t::integer_t command_status = smpp_pdu_1::command_status::rok; std::string error_reason; if( check_authentification( parsed.system_id(), parsed.password(), parsed.system_type() ) ) { // Роль TRANSMITTER-а должна быть разрешена и свободна. smpp_roles_accessor_t smpp_roles( *m_smpp_roles ); role_status_t & role( smpp_roles.transmitter() ); if( role.is_enabled() ) if( role.is_free() ) { // Клиент успешно подключился. role.acquire( m_channel ); so_change_state( "st_bound_tx" ); smpp_pdu_1::bind_transmitter_resp_t ok_resp; ok_resp.set_system_id( m_authentification.m_resp_system_id ); ok_resp.write_to( resp_pdu, what.sequence_number() ); } else { // Роль трансмиттера уже занята. command_status = smpp_pdu_1::command_status::rbindfail; error_reason = "TRANSMITTER уже подключен на другом канале"; } else { // Для этого SMPP-входа трансмиттер не может использоваться. command_status = smpp_pdu_1::command_status::rprohibited; error_reason = "режим TRANSMITTER запрешен"; } } else { command_status = smpp_pdu_1::command_status::rbindfail; error_reason = "не пройдена аутентификация"; } if( smpp_pdu_1::command_status::rok != command_status ) { so_log_1::err[ *this ] [ so_log_1::n() && "TRANSMITTER-у отказано в подключении" ] [ so_log_1::d() && error_reason ](); // Отсылаем клиенту отрицательный ответ и не меняем // своего состояния. smpp_pdu_1::bind_transmitter_resp_t fail_resp; fail_resp.write_to( resp_pdu, what.sequence_number() ); } resp_pdu.set_command_status( command_status ); send_pdu( resp_pdu ); }
Нужно было написать еще два метода: on_bind_receiver, on_bind_transceiver. В которых bind_transmitter_t нужно было заменить на bind_receiver_t; bind_transmitter_resp_t на bind_receiver_resp_t; smpp_roles.transmitter() на smpp_roles.receiver(); "st_bound_tx" на "st_bound_rx"; "TRANSMITTER" на "RECEIVER" и т.д.
Понятно, что тиражирование кода с последующей обработкой напильником в данном случае дело гиблое. Особенно, если со временем придется что-то подкрутить. В результате применения шаблонов код нужных методов получился вот таким:
void a_channel_t::on_bind_transmitter( const smpp_pdu_1::pdu_t & what ) { smpp_pdu_1::pdu_t resp_pdu( impl::process_bind< smpp_pdu_1::bind_transmitter_t >( what, *this, m_channel, m_authentification, *m_smpp_roles ) ); send_pdu( resp_pdu ); } void a_channel_t::on_bind_receiver( const smpp_pdu_1::pdu_t & what ) { smpp_pdu_1::pdu_t resp_pdu( impl::process_bind< smpp_pdu_1::bind_receiver_t >( what, *this, m_channel, m_authentification, *m_smpp_roles ) ); send_pdu( resp_pdu ); } void a_channel_t::on_bind_transceiver( const smpp_pdu_1::pdu_t & what ) { smpp_pdu_1::pdu_t resp_pdu( impl::process_bind< smpp_pdu_1::bind_transceiver_t >( what, *this, m_channel, m_authentification, *m_smpp_roles ) ); send_pdu( resp_pdu ); }А применены были вот такие шаблоны:
namespace impl { //! Указатель на функцию доступа к статусу SMPP роли. typedef role_status_t & (smpp_roles_accessor_t:: * role_accessor_t )(); //! Настройки на конкретные типы команд BIND. template& class Request > struct bind_traits_t {}; //! Настройки на BIND_TRANSMITTER. template<> struct bind_traits_t< smpp_pdu_1::bind_transmitter_t > { //! Тип ответа. typedef smpp_pdu_1::bind_transmitter_resp_tresponse_t; //! Функция для доступа к состоянию SMPP-роли. const role_accessor_trole_accessor; //! Имя состояния, в которое нужно перейти в случае успеха. const std::stringstate_for_success; //! Имя роли для логирования. const std::stringreadable_role_name; bind_traits_t< smpp_pdu_1::bind_transmitter_t >() : role_accessor( &smpp_roles_accessor_t::transmitter ) , state_for_success( "st_bound_tx" ) , readable_role_name( "TRANSMITTER" ) {} }; //! Настройки на BIND_RECEIVER. template<> struct bind_traits_t< smpp_pdu_1::bind_receiver_t > { //! Тип ответа. typedef smpp_pdu_1::bind_receiver_resp_tresponse_t; //! Функция для доступа к состоянию SMPP-роли. const role_accessor_trole_accessor; //! Имя состояния, в которое нужно перейти в случае успеха. const std::stringstate_for_success; //! Имя роли для логирования. const std::stringreadable_role_name; bind_traits_t< smpp_pdu_1::bind_receiver_t >() : role_accessor( &smpp_roles_accessor_t::receiver ) , state_for_success( "st_bound_rx" ) , readable_role_name( "RECEIVER" ) {} }; //! Настройки на BIND_TRANSCEIVER. template<> struct bind_traits_t< smpp_pdu_1::bind_transceiver_t > { //! Тип ответа. typedef smpp_pdu_1::bind_transceiver_resp_tresponse_t; //! Функция для доступа к состоянию SMPP-роли. const role_accessor_trole_accessor; //! Имя состояния, в которое нужно перейти в случае успеха. const std::stringstate_for_success; //! Имя роли для логирования. const std::stringreadable_role_name; bind_traits_t< smpp_pdu_1::bind_transceiver_t >() : role_accessor( &smpp_roles_accessor_t::transceiver ) , state_for_success( "st_bound_trx" ) , readable_role_name( "TRANSCEIVER" ) {} }; //! Шаблон для облегчения обработки BIND_* для разных типов SMPP ролей. /*! \return PDU с ответом для клиента. */ template< class Request > smpp_pdu_1::pdu_t process_bind( //! PDU с упакованным представлением BIND. const smpp_pdu_1::pdu_t & what, //! Агент, который обрабатывает BIND. so_4::rt::agent_t & agent, //! Канал, который обслуживат данный агент. const so_4::rt::comm_channel_t & channel, //! Параметры аутентификации. const authentification_cfg_t & authentification, //! Роли каналов SMPP. smpp_roles_t & smpp_roles ) { bind_traits_t< Request >traits; Request parsed( what ); log_parsed_pdu( agent, parsed ); typename bind_traits_t< Request >::response_tresp; smpp_pdu_1::command_header_t::integer_t command_status = smpp_pdu_1::command_status::rok; std::string error_reason; if( check_authentification( agent, authentification, parsed.system_id(), parsed.password(), parsed.system_type() ) ) { // Роль должна быть разрешена и свободна. smpp_roles_accessor_t smpp_roles( smpp_roles ); role_status_t & role( (smpp_roles.*(traits.role_accessor))() ); if( role.is_enabled() ) if( role.is_free() ) { // Клиент успешно подключился. role.acquire( channel ); agent.so_change_state( traits.state_for_success ); resp.set_system_id( authentification.m_resp_system_id ); } else { // Роль уже занята. command_status = smpp_pdu_1::command_status::rbindfail; error_reason = traits.readable_role_name + " уже подключен на другом канале"; } else { // Для этого SMPP-входа роль не может использоваться. command_status = smpp_pdu_1::command_status::rprohibited; error_reason = "режим " + traits.readable_role_name + " запрешен"; } } else { command_status = smpp_pdu_1::command_status::rbindfail; error_reason = "не пройдена аутентификация"; } if( smpp_pdu_1::command_status::rok != command_status ) { so_log_1::err[ agent ] [ so_log_1::n() << traits.readable_role_name << "-у отказано в подключении" ] [ so_log_1::d() << error_reason ](); } smpp_pdu_1::pdu_t resp_pdu; resp.write_to( resp_pdu, what.sequence_number() ); resp_pdu.set_command_status( command_status ); return resp_pdu; } } /* namespace impl */
В принципе, значения, которые определяются в специализации шаблона bind_traits_t можно было бы передавать в виде параметров в process_bind. Тогда бы методы on_bind_* имели бы приблизительно такой вид:
void a_channel_t::on_bind_transmitter( const smpp_pdu_1::pdu_t & what ) { smpp_pdu_1::pdu_t resp_pdu( impl::process_bind< smpp_pdu_1::bind_transmitter_t, smpp_pdu_1::bind_transmitter_resp_t >( what, *this, m_channel, m_authentification, *m_smpp_roles, &smpp_roles_accessor_t::transmitter, "st_bound_tx", "TRANSMITTER" ) ); send_pdu( resp_pdu ); }
и количество template-классов бы сократилось. Но я подумал, что эти параметры являются деталями реализации process_bind. Поэтому их можно не выносить на уровень сигнатуры функции process_bind. Что позволит, например, изменить реализацию process_bind не меняя ее прототип.
© 2003-2004 Е.А. Охотников
$LastChangedDate: 2004-12-04 10:20:14 +0300 (Sat, 04 Dec 2004) $