Exposable-паттерн. Независимые инжекции путём экспанирования

—

от автора

Disposable паттерн (интерфейс IDisposable) предполагает возможность высвобождения некоторых ресурсов, занимаемых объектом, путём вызова метода Dispose, ещё до того момента, когда все ссылки на экземпляр будут утрачены и сборщик мусора утилизирует его (хотя для надёжности вызов Dispose часто дублируется в финализаторе).

Но существует также обратный Exposable паттерн, когда ссылка на объект становится доступной до момента его полной инициализации. То есть экземпляр уже присутствует в памяти, частично проинициализирован и другие объекты ссылаются на него, но чтобы окончательно подготовить его к работе, нужно выполнить вызов метода Expose. Опять же данный вызов допустимо выполнять в конструкторе, что диаметрально вызову Dispose в финализаторе.

Само по себе наличие такой обратной симметрии выглядит красиво и естественно, но где это может пригодиться на практике постараемся раскрыть в этой статье.


Для справки, в C# существует директива using — синтаксический сахар для безопасного вызова метода Dispose.

using(var context = new Context()) { 	// statements } 

эквивалентно

var context = new Context(); try { 	// statements } finally { 	if (context != null) context .Dispose(); } 

с той лишь разницей, что в первом случае пересенная context становится read-only.

Unit of Work + Disposable + Exposable = Renewable Unit

Dispose-паттерну часто сопутствует паттерн Unit of Work, когда объекты предназначены для одноразового использовании, а время их жизни обычно короткое. То есть они создаются, тут же используются и затем сразу же освобождают занятые ресурсы, становясь непригодными для дальнейшего употребления.

Например, такой механизм часто применяется для доступа к сущностям базы данных через ORM-фреймворки.

using(var context = new DbContext(ConnectionString)) { 	persons =context.Persons.Where(p=>p.Age > minAge).ToList(); } 

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

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

Здесь может помочь совместное использование паттернов Disposable и Exposable. Вместо того, чтобы постоянно создавать и удалять объекты достаточно создать один объект, а затем в нём же занимать и освобождать ресурсы.

	context.Expose(); 	persons = context.Persons.Where(p=>p.Age > minAge).ToList(); 	context.Dispose(); 

Конечно, этот код не станет работать с существующими фреймворками, поскольку в них не предусмотрен метод Expose, но важно показать именно сам принцип — объекты можно использовать повторно, а необходимые ресурсы возобновлять динамически.

Независимые инжекции путём экспанирования (Independent Injections via Exposable Pattern)

Важно! Для полного понимания нижесказанного очень рекомендуется загрузить исходные коды (резервная ссылка) библиотеки Aero Framework с примером текстового редактора Sparrow, а также желательно ознакомиться с серией предыдущих статей.

Расширения привязки и xaml-разметки на примере локализации
Инжекторы контекста xaml
Командно-ориентированная навигация в xaml-приложениях
Совершенствуем xaml: Bindable Converters, Switch Converter, Sets
Сахарные инжекции в C#
Context Model Pattern via Aero Framework

Классический способ инжектирование вью-моделей в конструктор с помощью unit-контейнеров выглядит так:

public class ProductsViewModel : BaseViewModel {     public virtual void ProductsViewModel(SettingsViewModel settingsViewModel)     {     	// using of settingsViewModel     } }  public class SettingsViewModel : BaseViewModel {     public virtual void SettingsViewModel(ProductsViewModel productsViewModel)     {     	// using of productsViewModel     } } 

Но такой код вызовет исключение, поскольку невозможно проинициализировать ProductsViewModel пока не создана SettingsViewModel и наоборот.

Однако использование Exposable-паттерна в библиотеке Aero Framework позволяет элегантно решить проблему замкнутых зависимостей:

public class ProductsViewModel : ContextObject, IExposable {     public virtual void Expose()     {         var settingsViewModel = Store.Get<SettingsViewModel>();                  this[Context.Get("AnyCommand")].Executed += (sender, args) =>          {             // safe using of settingsViewModel         }     } }  public class SettingsViewModel : ContextObject, IExposable {     public virtual void Expose()     {         var productsViewModel = Store.Get<SettingsViewModel>();                  this[Context.Get("AnyCommand")].Executed += (sender, args) =>          {             // safe using of productsViewModel         }     } } 

Вкупе с механизмом сохранения состояния (Smart State, о котором чуть ниже) это даёт возможность безопасно проинициализировать обе вью-модели, ссылающиеся друг на друга, то есть реализовать принцип независимых прямых инжекций.

Умное состояние (Smart State)

Теперь мы подошли к весьма необычному, но в то же время полезному механизму сохранения состояния. Aero Framework позволяет очень изящно и непревзойденно лаконично решать задачи подобного рода.

Запустите десктоп-версию редактора Sparrow, который является примером приложения к библиотеке. Перед вами обычное окно, которое можно перетащить или изменить в размерах (визуальное состояние). Также можно создать несколько вкладок либо открыть текстовые файлы, а затем отредактировать в них текст (логическое состояние).

После этого закройте редактор (нажмите крестик на окне) и запустите его снова. Программа запустится ровно в том же визуальном и логическом состоянии, в котором её закрыли, то есть размеры и положение окна будут прежними, останутся открытыми рабочие вкладки и даже текст в них будет в таким же, каким его оставили при закрытии! Между тем исходные коды вью-моделей на первый взгляд не содержат никакой вспомогательной логики для сохранения состояния, как так получилось?

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

Всё, что нужно для этого выполнить, это проинициализировать необходимым образом фреймворк во время запуска приложения:

Unity.AppStorage = new AppStorage(); Unity.App = new AppAssistent(); 

По умолчанию сериализация происходит в файлы, но легко можно создать свою имплементацию и сохранять сериализованные объекты, например, в базу данных. Для этого нужно унаследоваться от интерфейса Unity.IApplication (по умолчанию имплементируется AppStorage). Что касается интерфейса Unity.IApplication (AppAssistent), то он необходим для культурных настроек при сериализации и в большинстве случаев можно ограничиться его стандартной реализацией.

Для сохранения состояния любого объекта, поддерживающего сериализацию, достаточно вызвать аттачед-метод Snapshot, либо воспользоваться вызовом Store.Snapshot, если объект находится в общем контейнере.

Мы разобрались с сохранением логического состояния, но ведь зачастую возникает необходимость хранения и визуального, к примеру, размеров и положения окон, состояния контролов и других параметров. Фреймворк предлагает нестандартное, но невероятно удобное решение. Что если хранить такие параметры в контекстных объектах (вью-моделях), но не в виде отдельных свойств для сериализации, а неявно, в виде словаря, где ключом является имя «мнимого» свойства?

На основе данной концепции родилась идея smart-свойств. Значение smart-свойства должно быть доступно через индексатор по имени-ключу, как в словаре, а классический get или set являются опциональными и могут отсутствовать! Эта функциональность реализована в классе SmartObject, от которого наследуется ContextObject, расширяя её.

Достаточно всего лишь написать в десктоп-версии:

public class AppViewModel : SmartObject // ContextObject {} 

<Window      x:Class="Sparrow.Views.AppView"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:viewModels="clr-namespace:Sparrow.ViewModels"     DataContext="{Store Key=viewModels:AppViewModel}"     WindowStyle="{ViewModel DefaultValue=SingleBorderWindow}"     ResizeMode="{Binding '[ResizeMode, CanResizeWithGrip]', Mode=TwoWay}"     Height="{Binding '[Height, 600]', Mode=TwoWay}"      Width="{ViewModel DefaultValue=800}"     Left="{ViewModel DefaultValue=NaN}"     Top="{Binding '[Top, NaN]', Mode=TwoWay}"     Title="{ViewModel DefaultValue='Sparrow'}"     Icon="/Sparrow.png"     ShowActivated="True"     Name="This"/> 

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

* О небольших нюансах и ограничениях некоторых других xaml-платформ, а также способах из обхода следует смотреть оригинальную статью Context Model Pattern via Aero Framework.

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

Итоги

Может показаться, что мы отклонились от основной темы, но это не так. Все, описанные в этой и предыдущих статьях, механизмы вместе с использованием паттерна Exposable позволяют создавать очень чистые и лаконичные вью-модели.

public class HelloViewModel : ContextObject, IExposable {     public string Message     {         get { return Get(() => Message); }         set { Set(() => Message, value); }     }      public virtual void Expose()     {         this[() => Message].PropertyChanged += (sender, args) => Context.Make.RaiseCanExecuteChanged();              this[Context.Show].CanExecute += (sender, args) => args.CanExecute = !string.IsNullOrEmpty(Message);          this[Context.Show].Executed += async (sender, args) =>         {             await MessageService.ShowAsync(Message);         };     } } 

То есть запросто может получиться так, что во вью-модели объявлено несколько свойств и только один метод Expose, а весь остальной функционал описыватся лямбда-выражениями! А если планируется дальнейшее наследование, то следует просто отметить метод модификатором virtual .

ссылка на оригинал статьи http://habrahabr.ru/post/256629/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *