Exposable pattern. Independent Injection by Expanse

  • Tutorial
Disposable pattern ( IDisposable interface ) implies the possibility of releasing some resources occupied by the object by calling the Dispose method , even before all references to the instance are lost and the garbage collector utilizes 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 in order to finally prepare it for work, you need to call the Expose method. Again, this call can be performed in the constructor, which is diametrical to the Dispose call in the finalizer.

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


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

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

equivalently

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

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

Unit of Work + Disposable + Exposable = Renewable Unit

Dispose -pattern is often accompanied by the Unit of Work pattern , when objects are intended for one-time use, and their life time is usually short. That is, they are created, immediately used and then immediately freed up 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();
}

A connection to the database is opened, the necessary request is made, and then it immediately closes. Keeping a connection open constantly is considered bad practice, as connection resources are often limited, and connections are automatically closed after a certain period of inactivity.

Everything is fine, but if we have a server with an uneven load, then during peak hours huge amounts of such instances of DbContext objects will be created , which will begin to affect the memory consumed by the server and speed, as the garbage collector will be called more often.

Sharing Disposable and Exposable patterns can help here.. Instead of constantly creating and deleting objects, it is enough to create one object, and then occupy and free resources in it.

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

Of course, this code will not work with existing frameworks, because they do not provide the Expose method , but it is important to show the principle itself - objects can be reused, and the necessary resources can be restored dynamically.

* As noted in the comment, perhaps this is not a good example, since the performance gain is quite controversial. But in order to better understand the essence of the pattern in question, we give the following reasoning.

In the usual sense, Disposable is the de-initialization and complete abandonment of an object. However, a link to it may well remain after calling Dispose. Often, access to most properties and methods will throw an exception if the programmer has provided for this, but you can easily use an instance as a key, call ToString , Equals, and some other methods. So why not expand your understanding of the Disposable pattern ? Let Dispose put the object in a standby state when it takes less resources to sleep! But then there must be a method that deduces from this state - Expose . Everything is very logical and logical. That is, we got some generalization of the Disposable pattern , and the scenario with the rejection of the object is just his special case.

Independent Injection by Expanse (Independent Injections via Exposable Pattern )

Important! For a complete understanding of the following, it is highly recommended to download the source codes ( backup link ) of the Aero Framework librarywith an example of the Sparrow text editor, and it is also advisable to familiarize yourself 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 to inject view models into a constructor usingunit containers look like this:

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 throw an exception because it is not possible to initialize the ProductsViewModel until the SettingsViewModel is 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();
        this[Context.Get("AnyCommand")].Executed += (sender, args) => 
        {
            // safe using of settingsViewModel
        }
    }
}
public class SettingsViewModel : ContextObject, IExposable
{
    public virtual void Expose()
    {
        var productsViewModel = Store.Get();
        this[Context.Get("AnyCommand")].Executed += (sender, args) => 
        {
            // safe using of productsViewModel
        }
    }
}

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

Smart state ( the Smart State )

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

Launch the Sparrow desktop editor, which is an example of an application to the library. Here is a regular window that you can drag or resize (visual state). You can also create several 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 in the same as they were left at the close! Meanwhile, the source code of the view models at first glance does not contain any auxiliary logic to maintain state, how did it happen?

On closer inspection, you may notice that the view models in the Sparrow application example are marked with the DataContract attribute , and some properties with the DataMember attribute , which allows you to use serialization and deserialization mechanisms to save and restore the logical state.

All that needs to be done for this is to initialize the framework as necessary during application launch:

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, in a database. To do this, you need to inherit from the Unity.IApplication interface ( AppStorage is implemented by default ). As for the Unity.IApplication ( AppAssistent ) interface , it is necessary for cultural settings during serialization, and in most cases you can limit yourself to its standard implementation.

To save the state of any object that supports serialization, just call the attached Snapshot method , or use the Store.Snapshot call if the object is in a common container.

We figured out maintaining the logical state, but often there is a need for storage and visual, for example, the size and position of windows, the state of controls and other parameters. The framework offers a non-standard, 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 smart- properties should be accessible through the indexer by key-name, as in the dictionary, and the classic get or set are optional and may be absent! This functionality is implemented in the class.SmartObject , from which ContextObject is inherited , extending it.

Just write in the desktop version:

public class AppViewModel : SmartObject // ContextObject
{}


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

* On the small nuances and limitations of some other xaml- platforms, as well as ways to bypass, see the original article Context Model Pattern via Aero Framework .

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

Summary

It may seem that we have deviated from the main topic, but this is not so. All the mechanisms described in this and previous articles, together with the use of 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 functionality is described by lambda expressions! And if further inheritance is planned, then you just need to mark the method with the virtual modifier .

Also popular now: