Опис завдання, ASP, Програмування, статті

Припустимо, у нас є ієрархія класів предметної області для подання інформації про фізичних та юридичних осіб. Відповідна діаграма класів приведена на Рис. 1.

Рис. 1 Діаграма класів фізичних / юридичних осіб

Є абстрактний клас BaseBody і два спадкоємця – PrivateBody (Фізична особа), CorporateBody (юридична особа). Всі особи мають цілочисельними унікальними ідентифікаторами (атрибут Id на діаграмі), можуть мати кілька телефонів і кілька адрес. Фізичні особи додатково характеризуються прізвищем, ім’ям, по батькові, датою народження. Юридичні особи – назвою, ІПН, БИК.

Об’єкти класів PrivateBody і CorporateBody серіалізуются в XML, серіалізовать подання може розміщуватися в базі даних, файлах на диску – сховище даних значення не має. Додаток, призначений для обліку клієнтів, повинно відновлювати об’єкти відповідних класів з XML.

Завдання XML-сериализации/десериализации об’єктів в. Net вирішується за допомогою класу XmlSerializer. У найпростішому випадку, коли нам апріорі відомий клас відновлюваного з потоку об’єкта:

	XmlSerializer deserializer = new XmlSerializer(typeof(PrivateBody));
	StringReader reader = new StringReader(xmlContent);
	PrivateBody body = (PrivateBody) deserializer.Deserialize(reader);  

Бажано, щоб процес відновлення об’єкта був поліморфним, то Тобто ми отримуємо потік, про який відомо, що він містить серіалізовать XML-представлення одного із спадкоємців базового класу BaseBody, і нам необхідно відновити об’єкт відповідного класу. Стандартними засобами XmlSerializer домогтися поліморфної десеріалізациі неможливо, так як в конструкторі XmlSerializer необхідно вказувати конкретний клас об’єктів, які будуть відновлюватися з потоку, при цьому якщо в потоці виявиться спадкоємець заданого класу, то XmlSerializer не зможе виконати відновлення, і виклик завершиться виняткової ситуацією. Таким чином, наступний код не придатний для використання:

	XmlSerializer deserializer = new XmlSerializer(typeof(BaseBody));
	StringReader reader = new StringReader(xmlContent);
	BaseBody body = (BaseBody) deserializer.Deserialize(reader);

Методика рішення

Для вирішення завдання поліморфної десеріалізациі об’єктів з XML розробимо свій допоміжний клас, назвемо його PolymorphousXmlSerializer. Нам знадобляться два статичних методу.

Метод Serialize є простою обгорткою виклику XmlSerializer.

	/// <summary> / / / Серіалізация об'єкта в потік
	/// </summary> / / /  Потік для сериализации  / / /  Об'єкт 
	public static void Serialize(TextWriter writer, object obj)
	{
		XmlSerializer serializer = new XmlSerializer(obj.GetType());
		serializer.Serialize(writer, obj);    
	}
	/// <summary> / / / Десеріалізациі об'єкта з потоку
	/// </summary> / / /  Потік для десеріалізациі  / / /  Базовий клас об'єкта  / / /  Використовувати чи спадкоємців  / / /  Об'єкт 
	public static object Deserialize(TextReader reader, Type baseType, 
bool useDerivatives)

Реалізація методу Deserialize трохи складніше, хоча в кінцевому рахунку все зводиться до виклику того ж XmlSerializer. Параметр useDerivatives визначає, чи слід використовувати поліморфний механізм десеріалізациі. Якщо useDerivatives = false, то виклик методу Deserialize повністю еквівалентний стандартному механізму роботи XmlSerializer.

Значно цікавіше виклик методу з useDerivatives = true. В цьому випадку ми припускаємо, що в якості baseType нам передали базовий клас, а потік може містити серіалізовать подання будь-якого спадкоємця baseType. Інформацію про те, який із спадкоємців baseType серіалізовать в потоці, можна отримати з назви кореневого тега XML – воно відповідає імені класу або значенням XmlTypeAttribute.TypeName, якщо для класу заданий вказаний атрибут сериализации. Знаючи базовий тип і назву кореневого тега XML, ми можемо визначити необхідний клас спадкоємця, потім створити XmlSerializer з передачею в якості параметра конструктору класу спадкоємця і викликати стандартний метод десеріалізациі. З урахуванням сказаного код методу Deserialize матиме такий вигляд:

	public static object Deserialize(TextReader reader, Type baseType,
 bool useDerivatives)
	{
		Type serializerType = null;
		string xmlContent = reader.ReadToEnd();
		if (useDerivatives)
		{
			Match rootMatch = RootElementRegex.Match(xmlContent);
			string rootName = rootMatch.Groups["1"].Value;
			serializerType = GetSerializerType(baseType, rootName);
		} 
		if (serializerType == null)
		{
			serializerType = baseType;
		}
		XmlSerializer deserializer = new XmlSerializer(serializerType);
		return deserializer.Deserialize(new StringReader(xmlContent));   
	}

Для визначення назви кореневого тега в XML використовується регулярне вираз:

/ / Регулярний вираз для пошуку назви root-елемента в XML файлі
	protected static readonly Regex RootElementRegex = 
new Regex("<s*(w+)", RegexOptions.Compiled);

Клас спадкоємця визначається за допомогою допоміжного методу
GetSerializerType.

	/// <summary> / / / Повертає тип, відповідний для сериализации об'єктів baseType  / / / З root-елементом rootName
	/// </summary> / / /  Базовий тип  / / /  root-елемент серіалізовать в XML / / / Об'єкта
/// </param>
	/// <returns></returns>
	public static Type GetSerializerType(Type baseType, string rootName)

У методі GetSerializerType нам необхідно знайти всіх спадкоємців від baseType, а потім вибрати того спадкоємця, серіалізовать XML-представлення якого буде мати кореневої елемент rootName. З допомогою Reflection ми можемо знайти всіх спадкоємців baseType в поточному домені додатки (AppDomain). Для збільшення швидкості роботи методу пошук спадкоємців базового класу будемо проводити тільки при першому виклику, а потім кешувати інформацію про типи у звичайній хеш-таблиці.

Для спрощення структури кеша і механізму пошуку ми будемо шукати спадкоємців не від baseType, а від класу, який є вершиною ієрархії (Після Object), в якій знаходиться і baseType. Вибір спадкоємця, відповідного rootName, здійснюватиметься на ім’я класу або за значенням атрибута XmlTypeAttribute.TypeName. Код методу GetSerializerType виглядає наступним чином:

	public static Type GetSerializerType(Type baseType, string rootName)
	{ / / Визначаємо базовий тип - вершину ієрархії
		Type theMostBaseType = baseType;
		while (!theMostBaseType.BaseType.Equals(typeof(object)))
		{
			theMostBaseType = theMostBaseType.BaseType;
		}
		Guid baseTypeGuid = theMostBaseType.GUID;
		if (BaseTypeDerivatives.Contains(baseTypeGuid))
		{
			return (Type) ((Hashtable) 
BaseTypeDerivatives[baseTypeGuid])[rootName];
		}
		else
		{
			AppDomain currentDomain = AppDomain.CurrentDomain;
			Assembly[] assemblies = currentDomain.GetAssemblies();
			Hashtable typeTable = new Hashtable(); 
			TypeFilter derivativesFilter = 
new TypeFilter(FilterDerivatives);
			foreach (Assembly assembly in assemblies)
			{
				Module[] modules = assembly.GetModules(false);
				foreach (Module m in modules)
				{
					Type[] types = m.FindTypes(derivativesFilter,
theMostBaseType);
					foreach (Type type in types)
					{
						XmlTypeAttribute typeAttribute = 
(XmlTypeAttribute)
 Attribute.GetCustomAttribute(type,
 typeof(XmlTypeAttribute));
						if ((typeAttribute != null) && 
(typeAttribute.TypeName != null) &&
(typeAttribute.TypeName != ""))
     typeTable.Add(typeAttribute.TypeName,
 type);
						else
							typeTable.Add(type.Name, type);
					}
				}
			}
			BaseTypeDerivatives[baseTypeGuid] = typeTable;
			return (Type) typeTable[rootName];
		}
}

В якості кеша інформації про типи використовується наступна хеш-таблиця:

	 / / Кеш для інформації про типи та їх спадкоємців
	protected static Hashtable BaseTypeDerivatives =
Hashtable.Synchronized(new Hashtable());

Додатково до класу PolymorphousXmlSerializer доданий метод для скидання кеша:

	/// <summary> / / / Скидання кешу з інформацією про типи та їх спадкоємців
	/// </summary>
	public static void ResetTypeCache()
	{
		BaseTypeDerivatives.Clear();
}

В процесі пошуку спадкоємців використовується callback-функція наступного вигляду:

	/// <summary> / / / Делегат для фільтрації спадкоємців заданого класу
	/// </summary> / / /  перевіряється тип  / / /  Критерій фільтрації 
	/// <returns></returns>
	protected static bool FilterDerivatives(Type type, object baseType)
	{
		return type.IsSubclassOf((Type) baseType);
	}

Ось власне і весь код, залишається тільки додати перевантажений метод Deserialize, який буде автоматично використовувати поліморфний механізм десеріалізациі.

	/// <summary> / / / Десеріалізациі об'єкта з потоку
	/// </summary> / / /  Потік  / / /  Базовий клас об'єкта  / / /  Об'єкт 
	public static object Deserialize(TextReader reader, Type baseType)
	{
		return Deserialize(reader, baseType, true);
	}

Тепер поліморфна десеріалізациі об’єктів фізичних та юридичних осіб буде мати вигляд:

	StringReader reader = new StringReader(xmlContent);
	BaseBody body = (BaseBody)PolymorphousXmlSerializer.Deserialize(reader,
typeof(BaseBody));

Висновок

Розглянута методика поліморфної десеріалізациі об’єктів з XML може мати безліч варіантів практичного застосування. У наступній статті буде розглянуто один нестандартний варіант використання поліморфної десеріалізациі в задачі конвертації проксі-класів для веб-сервісів.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*