oess_1.2.0. Наследование расширением (subclassing_by_extension)

Введение

Механизм subclassing_by_extension (наследование расширением) является дополнением механизма расширяемых типов (см. oess_1.2.0. Расширяемые типы). В частности, subclassing_by_extension возник для преодоление одной проблемы, которая не решалась с помощью расширяемых типов, но была очень актуальна для использования ObjESSty как средства сериализации в организации межпроцессовых взаимодействий. Ниже подробнее описывается данная проблема и реализованный в ObjESSty v.1.2.0 способ ее решения.

Проблема "неизвестных свойств"

В рамках одного процесса

Предположим, что есть некоторый полезный сервис. Например, предоставляющий возможность отсылки SMS на мобильный телефон. Этот сервис реализован в виде C++ библиотеки. Для отсылки SMS в нем предоставлена функция:
void
send_sms(
  const destination_t & dest,
  const destination_t & src,
  const sms_encoding_t & encoding,
  const std::string & sms_body )
throw( std::exception );
Если сервис окажется востребованным, то рано или поздно окажется, что интерфейс функции send_sms должен быть расширен. Например, для указания:

Изменение прототипа функции send_sms для удовлетворения возрастающих потребностей является тупиковым вариантом:

Возможность расширения списка параметров функции send_sms можно было бы осуществить с помощью т.к. карты свойств (traits map):

class trait_t
{
  public :
    virtual ~trait_t();

    virtual int
    trait_id() const = 0;
};

typedef std::map< int, trait_t * >  trait_map_t;

void
send_sms(
  const destination_t & dest,
  const destination_t & src,
  const sms_encoding_t & encoding,
  const std::string & sms_body,
  const trait_map_t * traits = 0 )
throw( std::exception );

Т.е. когда необходимо, скажем, поддержать в send_sms возможность таймаута ожидания результата доставки SMS и обеспечить приоритетную обработку, то создаются специальные классы свойств:

class timeout_trait_t
: public trait_t
{
  public :
    timeout_trait_t( unsigned int millisec );
    virtual ~timeout_trait_t();

    virtual int
    trait_id() const;

    unsigned int
    timeout() cont;
};

class priority_trait_t
: public trait_t
{
  public :
    priority_trait_t( int priority );
    virtual ~priority_trait_t();

    virtual int
    trait_id() const;

    int
    priority() cont;
};
которые используются затем, например, так:
trait_map_t traits;

timeout_trait_t timeout( 5000 );
traits[ timeout.trait_id() ] = &timeout;

priority_trait_t priority( -1 );
traits[ priority.trait_id() ] = &priority;

send_sms( dest, src, encoding, sms, &traits );

Между процессами

Рассмотрим теперь ситуацию, когда отсылкой SMS занимается серверный процесс, а инициированием -- клиентский. Для передачи запроса на сервер необходим какой-то объект, который будет сериализован клиентом и десериализован сервером. Например, пусть это будет тип send_sms_request_t:
class send_sms_request_t
: public oess_1::stdsn::serializable_t
{
  OESS_SERIALIZER( send_sms_request_t )
  private :
    destination_t m_dest;
    destination_t m_src;
    sms_encoding_t  m_encoding;
    std::string m_sms_body;
  ...
};
который на DDL описывается следующим образом:
{type	send_sms_request_t
	{attr	m_dest	{of destination_t}}
	{attr	m_src	{of destination_t}}
	{attr	m_encoding	{of sms_encoding_t}}
	{attr	m_sms_body	{of std::string}}
}

Когда придет время расширять функциональность запроса send_sms_request_t придется учитывать, что клиент и сервер могут поддерживать разные версии send_sms_request_t. Поэтому решение с передачей в запросе send_sms_request_t карты свойств в чистом виде становиться невозможным. Действительно, что произойдет, если описать send_sms_request_t следующим образом:

{type	trait_t
	{abstract}
}

{type	send_sms_request_t
	{attr	m_dest	{of destination_t}}
	{attr	m_src	{of destination_t}}
	{attr	m_encoding	{of sms_encoding_t}}
	{attr	m_sms_body	{of std::string}}
	{attr	m_traits
		{stl-map {key int}}
		{of {ptr} trait_t}
	}
}
Если клиент передаст в send_sms_request_t сериализованное представление неизвестного серверу свойства, то сервер не сможет десериализовать весь объект send_sms_request_t! Поскольку найдя во входящем потоке неизвестный объект ObjESSty не сможет определить, где же этот объект заканчивается, чтобы затем продолжить разбор оставшейся части потока.

Можно было бы сериализовать объекты по указателям так, чтобы сохранять размер сериализованного объекта. А затем использовать это значение для пропуска неизвестных объектов. Но тогда будут просто проигнорированы критически важные свойства. Например, если рассылка SMS по нескольким адресам осуществлятся при помощи свойства group_send_trait_t, то сервер, который не знает про это свойство, должен отказаться выполнять запрос. Например, пусть базовый класс trait_t будет не абстрактным, а содержащим атрибут m_must_understand, который показывает, можно ли игнорировать данное свойство:

{type	trait_t
	{attr	m_must_understand	{of oess_1::int_t}}
}
Более того, тип trait_t можно даже сделать расширяемым, чтобы в следующих версиях добавить в него общие для всех свойств атрибуты.

В таких случаях сервер должен быть способен извлечь из сериализованного неизвестного ему свойства ту часть, которая отностится к типу trait_t, а все остальное проигнорировать.

Именно эта возможность реализована в механизме subclassing_by_extension. С его помощью описанная ситуация решается следующим образом:

{type	trait_t
	{extensible}
	{subclassing_by_extension}

	{attr	m_must_understand	{of oess_1::uint_t}}
}

{type	send_sms_request_t
	{attr	m_dest	{of destination_t}}
	{attr	m_src	{of destination_t}}
	{attr	m_encoding	{of sms_encoding_t}}
	{attr	m_sms_body	{of std::string}}

	{attr	m_traits
		{stl-map {key int}}
		{of {extension_of} trait_t}
	}
}

{type	timeout_trait_t
	{extensible}
	{subclassing_by_extension {extension_of trait_t}}

	{attr	m_timeout	{of oess_1::uint_t}}
}

{type	priority_trait_t
	{extensible}
	{subclassing_by_extension {extension_of trait_t}}

	{attr	m_priority	{of oess_1::int_t}}
}

Описание механизма subclassing_by_extension

Идея

Для всех типов, которые допускают частичную десериализацию, должен быть единый корень в иерархии наследования. Именно этот общий для всех тип должен быть всегда десериализуем. ObjESSty строит вспомогательный код так, что атрибуты производных типов помещаются в специальную область расширения базового типа. Тогда десериализующая сторона извлекает сначала имя типа, который был реально сериализован. Если этот тип десериализующей стороне известен, то она выполяет его распаковку обычным образом. В противном случае десериализующая сторона создает объект базового типа и извлекает в него только содержимое атрибутов базового типа, а расширение, в котором находятся атрибуты сериализованного производного класса, пропускается.

Именно из-за того, что атрибуты производных типов сериализуются в специальное расширение базового типа, данный механизм получил название subclassing_by_extension.

Описание на DDL

На DDL все типы, которые участвуют в механизме subclassing_by_extension, должны отмечаться тегом {subclassing_by_extension}. У типа, являющегося вершиной иерархии расширяемых таким образом типов, указывается тег {subclassing_by_extension} без дополнительных подтегов:
{type	trait_t
	{extensible}
	{subclassing_by_extension}

	{attr	m_must_understand	{of oess_1::uint_t}}
}
У производных от него типов, в теге {subclassing_by_extension} должен быть указан подтег {extension_of} с именем базового типа:
{type	timeout_trait_t
	{extensible}
	{subclassing_by_extension {extension_of trait_t}}

	{attr	m_timeout	{of oess_1::uint_t}}
}

{type	priority_trait_t
	{extensible}
	{subclassing_by_extension {extension_of trait_t}}

	{attr	m_priority	{of oess_1::int_t}}
}

Для указания того, что атрибут является указателем на расширяемый наследованием тип, должен использоваться тег {extension_of} при указании типа атрибута:

{attr	m_traits
	{stl-map {key int}}
	{of {extension_of} trait_t}
}
Эта запись соответствует следующей конструкции C++:
std::map< int, trait_t * >  m_traits;

Ограничения

Механизм subclassing_by_extension требует, чтобы использовалось только одиночное наследование. Поэтому в теге {subclassing_by_extension} можно указать только один подтег {extension_of}.

Механизм subclassing_by_extension использует специальный формат сериализации, который отличается от формата сериализации обычного наследования. Поэтому, наследование в рамках subclassing_by_extension указывается с помощью комбинации {subclassing_by_extension {extension_of}}, а не с помощью {super}. В сочетании с возможностью использования только одиночного наследования это так же означает, что нельзя одновременно указывать {subclassing_by_extension {extension_of}} и {super} в описании типа. Между тем, вершина иерархии расширяемых наследованием типов может быть обычным образом производна от любых других типов:

{type	A ... }
{type	B ... }
{type	C
	|| Здесь можно использовать обычное наследование.
	{super A}
	{super B}
	{subclassing_by_extension}
	...
}
{type	D
	|| Здесь уже нельзя использовать обычное наследование
	|| и теги {super}.
	{subclassing_by_extension {extension_of C}}
	...
}

Вершина иерархии расширяемых наследованием типов не может быть абстрактным типом, т.к. объекты этого типа создаются на десериализующей стороне, если реальный сериализованный тип этой стороне не известен.

Если какой-то тип обычным образом унаследован от раширяемого наследованием типа, то объекты этого типа не должны использоваться в атрибутах {extension_of}. Т.е. следующая конструкция недопустима:

||
|| DDL
||
{type	A {subclassing_by_extension} ... }
{type	B
	{attr a {of {extension_of} A }}
}
{type	C {super A} ...}
//
// C++
//
B b;
b.a = new C(); // ОШИБКА!
К сожалению, на уровне С++ диагностировать эту ошибку нельзя. Она проявится только при попытке сериализовать объект b, т.к. для обычного и расширяющего наследования используются разные схемы сериализации.

Создание объекта ближайшего базового типа при десериализации

При десериализации типа, который не известен десериализирующей стороне ObjESSty предпринимает попытку создания объекта наиболее близкого базового типа. Например, если на десериализующей стороне есть иерархия:
{type	trait_t
	{subclassing_by_extension}
	... }
{type	cipher_t
	{abstract}
	{subclassing_by_extension {extension_of trait_t}}
	... }
{type	symmetric_cipher_t
	{subclassing_by_extension {extension_of cipher_t}}
	... }
{type	tdes_cipher_t
	{subclassing_by_extension {extension_of symmetric_cipher_t}}
	... }
{type	aes_cipher_t
	{subclassing_by_extension {extension_of symmetric_cipher_t}}
	... }

{type	request_t
	...
	{attr	m_traits
		{stl-map {key oess_1::uint_t}}
		{of {extension_of} trait_t}}
}
а на сериализующей стороне в атрибуте request_t::m_traits был сериализован объект типа:
{type	blowfish_cipher_t
	{subclassing_by_extension {extension_of symmetric_cipher_t}}
	... }
то при десериализации будет создан объект типа symmetric_cipher_t, т.к. в иерархии наследования он расположен ближе к blowfish_cipher_t, чем тип trait_t.

Для того, чтобы можно было создавать объекты ближайшего базового типа при десериализации, в сериализованном представлении ObjESSty сохраняет имена всех типов, которые составляют цепочку наследования. Т.е. для приведенного примера, в сериализованном представлении объекта типа blowfish_cipher_t будет сохранен список имен: {blowfish_cipher_t, symmetric_cipher_t, trait_t}.

Имена абстрактных типов, которые входят в иерархию наследования сериализуемого типа, в описании цепочки наследования не сохраняются, т.к. объекты абстрактных типов не могут быть созданы и десериализованны.

Расширяемость

Типы, входящию в иерархию расширяемых наследованием типов могут быть "просто расширяемыми" типами. Т.е. в их DDL описании могут использоваться теги {extensible} и {extension}. Например, пусть первая версия протокола межпроцессового взаимодействия описывается так:
{type
key_t
	{extensible}

	{attr m_index {of oess_1::uint_t}}
}

{type
value_t
	{extensible}
	{subclassing_by_extension}

	{attr	m_value {of std::string}}
}

{type
data_t

	{attr m_data {of {extension_of} value_t}
		{stl-map {key key_t}}}
}

{type
timeout_value_t
	{extensible}
	{subclassing_by_extension {extension_of value_t}}

	{attr	m_millisec {of oess_1::uint_t}}
}

{type
compression_value_t
	{extensible}
	{subclassing_by_extension {extension_of value_t}}

	{attr	m_preferred {of std::string}}
}
А вторая версия:
{type
key_t
	{extensible}

	{attr m_index {of oess_1::uint_t}}
}

{type
value_t
	{extensible}
	{subclassing_by_extension}

	{attr	m_value {of std::string}}

	{extension
		{attr m_version {of std::string}
			{default {c++ \"1.0\"}}}
	}
}

{type
data_t

	{attr m_data {of {extension_of} value_t}
		{stl-map {key key_t}}}
}

{type
timeout_value_t
	{extensible}
	{subclassing_by_extension {extension_of value_t}}

	{attr	m_millisec {of oess_1::uint_t}}

	{extension
		{attr	m_period {of oess_1::uint_t}
			{default {c++ 0 }}}
	}
}

{type
compression_value_t
	{extensible}
	{subclassing_by_extension {extension_of value_t}}

	{attr	m_preferred {of std::string}}

	{extension
		{attr	m_level {of oess_1::short_t}
			{default {c++ 9}}}
	}
}

{type
signature_value_t
	{extensible}
	{subclassing_by_extension {extension_of value_t}}

	{attr	m_algorithm {of std::string}}
}

Способ сериализации

При использовании двоичной сериализации (единственной доступной в ObjESSty 1.2.0) объект:
{type	A
	{subclassing_by_extension}

	{attr	a {of oess_1::uint_t}}
	{attr	b {of oess_1::uint_t}}
}
Будет сериализован в виде следующей псевдо-структуры:
struct  A_bin_image
{
  oess_1::uint_t  a;
  oess_1::uint_t  b;
  oess_1::uint_t  derived_image_size;
  oess_1::char_t  derived_image[ derived_image_size ];
};

Производный при помощи расширения тип B:

{type	B
	{subclassing_by_extension {extension_of A}}

	{attr	c {of oess_1::uint_t}
}
будет представлен псевдо-структурой A_bin_image, у которой в атрибуте A_bin_image::derived_image будет находится псевдо структура:
struct  B_bin_image
{
  oess_1::uint_t  c;
  oess_1::uint_t  derived_image_size;
  oess_1::char_t  derived_image[ derived_image_size ];
}

Соответственно, в B_bin_image::derived_image могут находиться аналогичные структуры для производных от B классов.

Если же типы A и B являются так же и "просто расширяемыми":

{type	A
	{extensible}
	{subclassing_by_extension}

	{attr	a {of oess_1::uint_t}}
	{attr	b {of oess_1::uint_t}}
}
{type	B
	{extensible}
	{subclassing_by_extension {extension_of A}}

	{attr	c {of oess_1::uint_t}
}
то псевдо-структуры A_bin_image и B_bin_image имеют вид:
struct  A_bin_image
{
  oess_1::uint_t  a;
  oess_1::uint_t  b;
  oess_1::uint_t  extension_size;
  oess_1::char_t  extension[ extension_size ];
  oess_1::uint_t  derived_image_size;
  oess_1::char_t  derived_image[ derived_image_size ];
};
struct  B_bin_image
{
  oess_1::uint_t  c;
  oess_1::uint_t  extension_size;
  oess_1::char_t  extension[ extension_size ];
  oess_1::uint_t  derived_image_size;
  oess_1::char_t  derived_image[ derived_image_size ];
}

Документация по ObjESSty. Последние изменения: Fri Oct 13 18:35:37 2006. Создано системой  doxygen 1.4.7
Hosted by uCoz