четверг, 25 февраля 2016 г.

Репозиторий-синглтон, или как сделать статический класс с отложенной инициализацией

Доброго времени суток!

Сегодня поговорим немного о псевдо-синглтоне на статических классах в C#.

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

Писать свой класс для каждого типа ресурсов с похожей функциональностью - не интересно, муторно и скучно.

А не сделать ли нам статический обобщенный класс? Продолжение под катом.

Приступаем к делу

Как всегда, на досуге, у меня появилась замечательная идея сделать игру, написать для нее свой движок на MonoGame, несколько инструментов для редактирования ресурсов.

Ресурсы - экземпляры собственных классов, хранят различные примитивные данные. У них у всех общий родитель - абстрактный класс UniqueEntity. Вот его код:


Все достаточно просто.

Теперь хорошо бы было сделать репозиторий для экземпляров классов, которые унаследованы от класса UniqueEntity. И тут в голову пришла идея... А не сделать ли нам статичный обобщенный класс?

Отложенная инициализация статического класса

Все, кто когда либо работал со статическими классами, знают основные принципы статических конструкторов. Приведем выдержку с msdn (источник):
Статические конструкторы обладают следующими свойствами.
  • Статический конструктор не принимает модификаторы доступа и не имеет параметров.
  • Статический конструктор вызывается автоматически для инициализации класса перед созданием первого экземпляра или ссылкой на какие-либо статические члены.
  • Статический конструктор нельзя вызывать напрямую.
  • Пользователь не управляет тем, когда статический конструктор выполняется в программе.
Все достаточно прозрачно. Основная идея синглтона заключается в том, что он обладает отложенной инициализацией. Каноничная форма синглтона на C# создает экземпляр класса в момент первого обращения к нему. А что если нам сделать статический класс обобщенным?

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

Отсюда вывод: у нас будет несколько псевдо-экземпляров статического класса! Магия? Не думаю.

Ближе к делу

Полный код класса будет приведен в конце статьи. Для начала рассмотрим прототип менеджера ресурсов (т.е. репозитория):

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

Отдельно заведем переменную для хранения последнего идентификатора ресурса. При добавлении нового ресурса она будет увеличиваться на 1 и присваиваться новому ресурсу.

Предположим, мы сделали сохранение и загрузку при помощи стандартной бинарной сериализации. Она не быстрая, но простая в использовании - раз, и готово. Ну а что если надо сделать сериализацию в XML? Json?

Надо добавить возможность выбора способа сериализации. Для этого мы воспользуемся паттерном "стратегия".

Кастомизация сериализации

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


Далее добавим метод, при помощи которого будем указывать способ сериализации и поле, в котором будем хранить эту информацию.

Что же за тип FormatterType.Custom? В случае, если установлен этот вид сериализации, менеджер ресурсов должен использовать какой-то собственный способ сериализации. Для этого определим обобщенный интерфейс некоторой фабрики, которая умеет сохранять, загружать и возвращать имя файла ресурсов.

Если необходимо сделать свой собственный сериализатор для каких либо объектов, создается класс, который реализует интерфейс IResourceManagerFactory<T>, а экземпляр этого класса регистрируется в ResourceManager.

В соответствии с введенными выше модификациями, прототип нашего менеджера выглядит теперь примерно вот так вот:


Как и обещал, вот весь код класса ResourceManager<T>.

http://pastebin.com/pXVpreWd

Всем спасибо за внимание и до новых встреч!

P.S. Как можно заметить, я сделал его потокобезопасным, так как в моем случае использовалась активно многопоточность.

Комментариев нет:

Отправить комментарий