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/
Добавить комментарий