📜 ⬆️ ⬇️

Exposable pattern. Independent injections by expansion

Disposable pattern ( IDisposable interface) suggests the possibility of releasing some of the resources occupied by an object by calling the Dispose method even before all references to the instance are lost and the garbage collector recycles it (although for reliability, the Dispose call is often duplicated in the finalizer).

But there is also a reverse Exposable pattern, when a reference to an object becomes available until it is fully initialized. That is, the instance is already present in memory, partially initialized, and other objects refer to it, but to finally prepare it for work, you need to call the Expose method. Again, this call can be performed in the constructor, which is diametrically called Dispose in the finalizer.

In itself, the presence of such a return symmetry looks beautiful and natural, but where it can be useful in practice, we will try to reveal in this article.
')

For reference, in C # there is a using directive - syntactic sugar for safely calling the Dispose method.

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

is equivalent to

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

the only difference is that in the first case the context variable becomes read-only .

Unit of Work + Disposable + Exposable = Renewable Unit

Dispose of the pattern often accompanies the Unit of Work pattern when objects are designed for one-time use, and their lifetimes are usually short. That is, they are created, immediately used, and then immediately release the occupied resources, becoming unsuitable for further use.

For example, such a mechanism is often used to access database entities through ORM frameworks.

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

The connection to the database is opened, the necessary request is made, and then it is immediately closed. Keeping a connection permanently open is considered a bad practice, since often the resource of connections is limited, and connections are automatically closed after a certain period of inactivity.

Everything is good, but if we have a server with an uneven load, then during peak hours, user requests will create huge numbers of such instances of DbContext objects, which will have an effect on the memory consumed by the server and speed, as the garbage collector will be called more often.

Here, sharing the Disposable and Exposable patterns can help. Instead of constantly creating and deleting objects, it is enough to create one object, and then in it to occupy and release resources.

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

Of course, this code will not work with existing frameworks, since the Expose method is not provided in them, but it is important to show the principle itself - objects can be reused, and the necessary resources can be resumed dynamically.

* As noted in the comment, perhaps this is not the most successful example, since the performance gain is quite controversial. But in order to better grasp the essence of the pattern in question, we present the following reasoning.

In the usual sense of Disposable - deinitialization and complete abandonment of the object. However, a reference to it may well remain after calling Dispose . Often, accessing most properties and methods will cause an exception if the programmer has provided this, but the instance can usually be easily used as a key, called ToString , Equals, and some other methods. So why not expand the understanding of the Disposable pattern? Let Dispose put an object into a state of alert, when it takes up less resources, to sleep! But then there must be a method out of this state - Expose . Everything is very logical and logical. That is, we have received some generalization of the Disposable pattern, and the scenario of abandoning an object is only its special case.

Independent Injections via Exposable Pattern

Important! For a complete understanding of the above, it is highly recommended to download the source code ( backup link ) of the Aero Framework library with the example of the text editor Sparrow , and also it is advisable to get acquainted with the series of previous articles.

Binding and xaml markup extensions using localization as an example
Xaml context injectors
Command-Oriented Navigation in Xaml Applications
Improving xaml: Bindable Converters, Switch Converter, Sets
Sugar injections in C #
Context Model Pattern via Aero Framework

The classic way of injecting view models into a constructor using unit containers is as follows:

 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 } } 

But such code will cause an exception, since it is impossible to initialize the ProductsViewModel until the SettingsViewModel has been created and vice versa.

However, using the Exposable pattern in the Aero Framework library allows you to elegantly solve the problem of closed dependencies:

 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<ProductsViewModel>(); this[Context.Get("AnyCommand")].Executed += (sender, args) => { // safe using of productsViewModel } } } 

Together with the state-saving mechanism ( Smart State , which is just below), this makes it possible to safely initialize both view models that reference each other, that is, to implement the principle of independent direct injections .

Smart State

Now we come to a very unusual, but at the same time useful mechanism for maintaining state. Aero Framework allows you to very elegantly and unmatchedly concisely solve problems of this kind.

Run the desktop version of the Sparrow editor, which is an example library application. This is a normal window that can be dragged or resized (visual state). You can also create multiple tabs or open text files, and then edit the text in them (logical state).

After that, close the editor (click the cross on the window) and run it again. The program will start exactly in the same visual and logical state in which it was closed, that is, the size and position of the window will be the same, the working tabs will remain open and even the text in them will be the same as it was left when closed! Meanwhile, the source codes of the view models at first glance do not contain any auxiliary logic for saving the state, how did it happen?

Upon careful consideration, you may notice that the view models in the Sparrow sample application are marked with the DataContract attribute, and some properties with the DataMember attribute , which allows you to use serialization and deserialization mechanisms to preserve and restore the logical state.

All you need to do to do this is to initialize the necessary framework during the launch of the application:

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

By default, serialization occurs in files, but you can easily create your own implementation and save serialized objects, for example, to a database. For this you need to inherit from the Unity.IApplication interface ( AppStorage is implemented by default ). As for the interface Unity.IApplication ( AppAssistent ), it is necessary for cultural settings during serialization and in most cases it can be limited to its standard implementation.

To save the state of any object that supports serialization, simply call the Snapshot attachment method, or use the Store.Snapshot call if the object is in the general container.

We have dealt with the preservation of the logical state, but it often becomes necessary to store and visually, for example, the size and position of the windows, the state of the controls and other parameters. The framework offers a custom, but incredibly convenient solution. What if you store such parameters in context objects (view models), but not as separate properties for serialization, but implicitly, as a dictionary, where the key is the name of the “imaginary” property?

Based on this concept, the idea of smart properties was born. The value of the smart property must be accessible through the indexer by the key name, as in a dictionary, and the classic get or set are optional and may be missing! This functionality is implemented in the SmartObject class, from which ContextObject inherits, extending it.

Just write in the desktop version:

 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"/> 

after which the size and position of the window will be automatically saved when you exit the application and are exactly restored at startup! Agree, this is an amazing conciseness for solving this kind of problem. I did not have to write a single extra line of code in the view model or code-behaine.

* For small nuances and limitations of some other xaml platforms, as well as workarounds, see the original article Context Model Pattern via Aero Framework .

Thanks to the polymorphism mechanism, the validation of property values ​​using the implementation of the IDataErrorInfo interface, which also uses an indexer, fits neatly into the concept of smart state.

Results

It may seem that we have deviated from the main theme, but this is not so. Everything described in this and previous articles, mechanisms together using the Exposable pattern , allow you to create very clean and concise view-models.

 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); }; } } 

That is, it can easily happen that several properties and only one Expose method are declared in the view model, and the rest of the functional is described by lambda expressions! And if further inheritance is planned, then you should simply mark the method with the virtual modifier.

Source: https://habr.com/ru/post/256629/


All Articles