Click here to Skip to main content
15,881,281 members
Articles / Programming Languages / C#

Separation of Concerns and Smart Mixins with the help of Roxy IoC Container and Code Generator

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
10 Apr 2018Apache21 min read 11.4K   90   20  
Achieving separation of concerns using Roxy IoC Container and Code Generator.

Introduction

Separation of Concerns

In software engineering a concern is something that is responsible for a single 'side' of the functionality. I plan to give concrete coding examples of separate concerns later in this article, but for now, let us have a purely verbal discussion (without looking at the code).

Assume that you have some visual glyph on a canvas (it does not matter if it is a desktop or a web application and what software package is used). Here are some glyph related concerns listed and explained:

  1. The glyph carries our some Visual information - its color, size, position, visual content - this is its Visual concern.
  2. In most applications, glyphs also correspond to some useful data that is presented by the glyph or modified via the glyph or both. The data and the functionality to display or modify it will represent the Data concern of the glyph.
  3. Assume that we can remove the glyph from the canvas - the 'remove' functionality represents Removal concern of the glyph.
  4. Assume also that the glyph can be in selected and unselected states - its visuals change depending on whether it is selected or not. The functionality responsible for it can be factored out as a Selection concern.

 

To illustrate, run the MultiConcernsTest sample application. I'll explain the code later, but now let us discuss the application features.

When you start the app, you'll see the following window:

Image 1

The application lists two business groups "Astrologists" and "Alchemists" within an organization, with two people under each group.

Clicking on the top beige part containing the group name will select the group (its border will become thicker). Clicking on a person will select that person and that group. Only one group can be selected at a time and only one person can be selected within a group at a time. If you unselect a group - all its people will also get unselected.

You can use the left mouse button to open a menu containing "Remove" menu item. If you click on it, the corresponding glyph will be removed - if you click on the business group's "Remove" you'll remove the whole business group, while if you click on a person's "Remove" you will only remove that person within the business group.

Clearly for a person glyph, the visual concern is represented by the actual visual control and its functionality. The Data concern consists of the functionality containing first and last name. The Selection and Removal concerns are represented by the functionality in charge of selecting and removing the glyph.

Similar split into concerns can be done on the business group glyph level.

Note that the listed concerns are almost independent of each other - The only points of interdependence are:

  • When an item is removed it should also be removed visually - WPF (which I used to implement the samples) takes care of it automatically - the View is being modified once the View Model changes.
  • When an item is selected, its border should become thicker - this is achieved by some small extra XAML functionality.

 

In the discussion below I'll skip talking about Visual - Non-Visual concerns split. WPF and other frameworks are very good at taking care of this particular concern split (if you use MVVM pattern). I'll be talking mostly about splitting Data, Selection and Removal concerns.

The best design is to separate the concerns into separate classes/interfaces, build and test them completely separately and then put them together with little or no extra code.

Such separation of concerns has the following major advantages:

  1. Reuse - Selection and Removal functionality can be factored out into separate classes and reused for different glyphs and even for some non-visual code.
  2. Code Clarity - when data, selection and removal functionality are all mixed together they pollute the code so that it is difficult to tell where one ends and other begins.
  3. Code Independence - if people start placing various concerns together they will unavoidably create more dependency between various concerns. This will lead to various inter-dependency bugs when changing code corresponding to one concern might result in a bug in another.
  4. Testing - it is much easier and better to test all concerns separately rather than together. Understandably - there are fewer combinations if you test individual concerns rather than their Cartesian product.

 

Behaviors

C# behavior is a class that allows to modify the behavior of another class. Behaviors are attached to an object of a class they modify. They may update the object's properties and also add handlers to its events whenever they are attached to it.

Most people are only familiar with visual WPF behaviors, however, non-visual behaviors can also be very useful as I showed in a number of articles e.g. View-View Model based WPF and XAML Implementational Patterns. and Collection Behaviors. Behaviors are very useful when it comes to the separation of concerns since they allow to factor out a specific concern functionality into a behavior class.

Later in the article I will provide specific examples of separation of concerns using behaviors.

Roxy

Roxy is a code generator and an IoC container which I built specifically with separation of concerns in mind. The main point of this article is to demonstrate the advantage of using Roxy in easily and cleanly achieving separation of concerns.

Roxy is an open source project available at Github at Roxy.

One can use the nuget manager to download NP.Roxy and all the dlls required by it from nuget.org.

Usage and Implementation Inheritance and Smart Mixins

Before diving into samples, let us review the OOP notion of inheritance and a newer notion of mixins.

When we talk about inheritance, we essentially talk about two different (though connected) notions - Usage Inheritance and Implementation Inheritance.

In C# and Java, Usage Inheritance is represented by interfaces and by abstract or virtual super class members. C# and Java interface inheritance is as powerful as it can be. In particular, it allows the 'smart' multiple Usage Inheritance so that the properties, methods and events from several interfaces are merged into one as long as their signatures and names match.

The much spoken about "Is A" relationship is related precisely to the Usage Inheritance and has almost nothing to do with the Implementation Inheritance - indeed it is the Usage Inheritance that defines the object's usage.

The Implementation Inheritance in C# and Java is not powerful at all. Only single inheritance is allowed, and correspondingly there is no smart merging of various class member implementations.

In general, the class functionality can be demonstrated by the following picture:

Image 2

The Usage is represented by the border of the shapes. The empty (white) or shaded areas represent correspondingly abstract or virtual members (properties or methods or even sets of them). Different white areas can represent the areas of different usage concerns.

Making the class work the way it should can be achieved by plugging the functionality implementations into the empty (and/or) shaded areas of the class shape (the border of the area determines its Usage interface).

This is what I think the purpose of the ideal Implementation Inheritance should be (just to fill in the blanks).

In my view the 'ideal' multiple Implementation Inheritance should provide the following features:

  1. It should provide the functionality to easily specify multiple Implementation 'super classes'. Several Implementation 'super classes' can even be of the same type - their functionality may be used to implement the various plugins (blank areas on the picture above).
  2. Instead of the 'white box' inheritance when everything of the 'super class' goes into the subclass, it should be a 'black box' inheritance under which the developer is required to specify what member of the 'super class' should be accessible in the subclass.
  3. The easiest way of matching the abstract or virtual 'blank' with some 'super class' functionality is by name - therfore developers should be allowed to easily rename the 'super class' members within a subclass.

 

Unfortunately no language natively supports this type of inheritance - C++ multiple implementation inheritance is very far from 'ideal' and consequently can lead to numerous problems and is considered to be too difficult to use.

The best place to implement such Implementation Inheritance is to make it part of new language features. Making it part of the language will ensure type safety and allow compiler optimizations. I plan to talk more about it in future articles. In the meanwhile, since we do not have such features built in, we can implement this functionality using the Wrappers/Adapters concept (see e.g. Implementing Adapter Pattern and Imitating Multiple Inheritance in C# using Roslyn based VS Extension Wrapper Generator).

In many other languages such 'wrapper' constructs are called 'mixins'. One of the main purposes of Roxy is to enable 'mixin' functionality in C#. Moreover, as I plan to demonstrate in this article and expound in subsequent articles, Roxy mixins are 'smart' in a sense that they allow to change the name of the 'super class' member within a 'subclass' and merge multiple 'super class' members into a single 'subclass' member.

Samples

Code Location

Code can be downloaded from the top of this article (NP.Concerns.Demo.zip) file, it is also available on Github: NP.Concerns.Demo

Demo Application and Brief Description of the Samples

Every sample demonstrates building the same small WPF application:

Image 3

It displays an organization with 2 departments - "Astrologists" and "Alchemists" each of which contains 2 people. You can select a single department or a single person within that department (selected card has a thicker border).

When you select a person, that person's department also gets selected. When a department is unselected - all its people get also get unselected.

The image above has "Alchemists" department and "Michael Mont" within it selected.

If you right click on a department or person card, you'll get a context menu allowing you to remove the card (whether a whole department or just a person within a department).

As was mentioned above, we almost do not concern ourselves with how the visuals are implemented - we shall be concentrating primarily on the View Models.

There are 3 demo samples:

  1. MultiConcernsTest - provides the plainest implementation of the application. I minimize the usage of the behaviors (even though I do use some of them to shrink the code a little).
  2. MultiConcernsBehaviorTest - similar to the first sample, but I factor out the parent-child selection behavior into a separate class to demonstrate advantages of behavior-based separation of concerns.
  3. MultiConcernsRoxyTest - shows how to build the same application using Roxy. There is almost no inter-concern code - the concerns are being plugged in from separate classes and almost everything is achieved via the configuration.

 

MultiConcernsTest

Take a look at MultiConcernsTest.sln solution and run the a application. Make sure the application behaves as was described above: the selection, and removal work.

All of the business logic of the application is implemented via the non-visual View Model code. The View Model code is located under "ViewModels" project folder.

There are two central View Model classes: PersonVM and BusinessGroupVM. There is also a small class BusinessGroupsVM which is a collection of business groups. Note that I used SingleSelectionObservableCollection class for the collection - it provides the functionality to ensure that no more that one member of the collection is in "Selected" state. This class is defined within NP.Utilities project referenced by our WPF application. The BusinessGroupsVM class also contains collection behavior to facilitate removal of an item from the collection - more on it later.

The test data based on the above classes is created within MainWindow.xaml.cs class and is assigned to be the DataContext of the main window:

public MainWindow()
{
    InitializeComponent();

    BusinessGroupsVM businessGroups = new BusinessGroupsVM();

    // build some test data
    BusinessGroupVM businessGroup1 = new BusinessGroupVM
    {
        Name = "Astrologists"
    };

    businessGroup1.People.Add(new PersonVM { FirstName = "Joe", LastName = "Doe" });
    businessGroup1.People.Add(new PersonVM { FirstName = "Jane", LastName = "Dane" });

    businessGroups.Add(businessGroup1);

    BusinessGroupVM businessGroup2 = new BusinessGroupVM
    {
        Name = "Alchemists"
    };

    businessGroup2.People.Add(new PersonVM { FirstName = "Michael", LastName = "Mont" });
    businessGroup2.People.Add(new PersonVM { FirstName = "Michelle", LastName = "Mitchell" });

    businessGroups.Add(businessGroup2);

    // assign business groups to be the DataContext. 
    this.DataContext = businessGroups;
}  

Take a look at the PersonVM class. Various (non-data) related concerns are basically provided by the interfaces that the class implements: INotifyPropertyChanged, IRemovable and ISelectableItem<PersonVM>.

Image 4

Take a look at IRemovable interface defined within NP.Utilities project:

public interface IRemovable
{
    event Action<iremovable> RemoveEvent;

    void Remove();
}  
</iremovable>

It is extremely simple - it has method Remove() which is supposed to fire RemoveEvent. Then a collection that the IRemovable item belongs to can remove the item from itself.

And here is the ISelectableItem interface:

public interface ISelectableItem<T>
    where T : ISelectableItem<T>
{
    bool IsSelected { get; set; }

    event Action<ISelectableItem<T>> IsSelectedChanged;

    void SelectItem();
}  

IsSelected property specifies whether the item is selected or not. Whenever it changes, IsSelectedChanged event should fire. SelectItem() method should change the IsSelected property to true.

Now, let us go back to the PersonVM code - I was using C# regions to separate the concerns within the code:

public class PersonVM : 
    INotifyPropertyChanged,
    IRemovable,
    ISelectableItem<PersonVM>
{
    #region Data_Concern_Region
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName => FirstName + " " + LastName;
    #endregion Data_Concern_Region

    #region Notifiable_Concern_Region
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion Notifiable_Concern_Region


    #region Removeable_Concern_Region
    public event Action<IRemovable> RemoveEvent = null;

    public void Remove()
    {
        RemoveEvent?.Invoke(this);
    }
    #endregion Removeable_Concern_Region


    #region Selectable_Concern_Region
    public event Action<ISelectableItem<PersonVM>> IsSelectedChanged;

    bool _isSelected = false;
    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            if (_isSelected == value)
                return;

            _isSelected = value;

            IsSelectedChanged?.Invoke(this);

            OnPropertyChanged(nameof(IsSelected));
        }
    }

    public void ToggleSelection()
    {
        this.IsSelected = !this.IsSelected;
    }

    public void  SelectItem()
    {
        this.IsSelected = true;
    }

    #endregion Selectable_Concern_Region
}  

You can see that the data concern is defined within the class, while the rest of the concerns are essentially defined by the interfaces and the functionality that implements them.

Now take a look at BusinessGroupVM class. Here is its class diagram

Image 5

It has all similar concerns and similar implementations of Removable and Selectable concerns. The Notifiable concern (with PropertyChanged event) is implemented in the super class - VMBase - I could have also used VMBase as a base class for PersonVM but I wanted to show its implementation as a separate concern. Here is the BusinessGroupVM code:

public class BusinessGroupVM : VMBase, IRemovable, ISelectableItem<BusinessGroupVM>
{
    #region Data_Concern_Region
    public string Name { get; set; }

    public SingleSelectionObservableCollection<PersonVM> People { get; } =
        new SingleSelectionObservableCollection<PersonVM>();
    #endregion Data_Concern_Region


    #region Removeable_Concern_Region
    public event Action<IRemovable> RemoveEvent = null;

    public void Remove()
    {
        RemoveEvent?.Invoke(this);
    }
    #endregion Removeable_Concern_Region


    #region Selectable_Concern_Region
    public event Action<ISelectableItem<BusinessGroupVM>> IsSelectedChanged;

    bool _isSelected = false;
    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            if (_isSelected == value)
                return;

            _isSelected = value;

            IsSelectedChanged?.Invoke(this);

            OnPropertyChanged(nameof(IsSelected));
        }
    }

    public void ToggleSelection()
    {
        this.IsSelected = !this.IsSelected;
    }

    public void SelectItem()
    {
        this.IsSelected = true;
    }

    #endregion Selectable_Concern_Region

    IDisposable _behaviorsDisposable;
    public BusinessGroupVM()
    {
        _behaviorsDisposable = 
            this.People.AddBehavior // remove behavior
            (
                (person) => person.RemoveEvent += Person_RemoveEvent,
                (person) => person.RemoveEvent -= Person_RemoveEvent
            )
            .AddBehavior // select behavior
            (
                (person) => person.IsSelectedChanged += Person_IsSelectedChanged,
                (person) => person.IsSelectedChanged -= Person_IsSelectedChanged
            );

        this.IsSelectedChanged += BusinessGroupVM_IsSelectedChanged;
    }

    private void Person_RemoveEvent(IRemovable person)
    {
        this.People.Remove((PersonVM) person);
    }

    private void BusinessGroupVM_IsSelectedChanged(ISelectableItem<BusinessGroupVM> businessGroup)
    {
        if (!this.IsSelected)
        {
            // this will also set all the 
            // all the Person objects within 
            // People collection into not-selected state.
            foreach(PersonVM personVM in this.People)
            {
                personVM.IsSelected = false;
            }
        }
    }

    private void Person_IsSelectedChanged(ISelectableItem<PersonVM> person)
    {
        if (person.IsSelected)
            this.IsSelected = true;
    }
}  

There are two CollectionBehaviors defined on the class to react on removal and selecting a person:

public BusinessGroupVM()
{
    _behaviorsDisposable = 
        this.People.AddBehavior // remove behavior
        (
            (person) => person.RemoveEvent += Person_RemoveEvent,
            (person) => person.RemoveEvent -= Person_RemoveEvent
        )
        .AddBehavior // select behavior
        (
            (person) => person.IsSelectedChanged += Person_IsSelectedChanged,
            (person) => person.IsSelectedChanged -= Person_IsSelectedChanged
        );

    this.IsSelectedChanged += BusinessGroupVM_IsSelectedChanged;
} 

Collection behaviors are described in my Collection Behaviors article. They allow adding event handlers on every item within an ObservableCollection when the item is added to it and removing the corresponding event handlers when and if the item is removed.

The constructor code above, means that PersonVM object's RemoveEvent will have Person_RemoveEvent handler and IsSelectedChanged will have Person_IsSelectedChanged handler as long as the person belongs to the People collection of BusinessGroupVM object.

You can see that Person_RemoveEvent simply removes the person from the People collection and Person_IsSelectedChanged ensures that the BusinessGroupVM object is selected whenever one of its PersonVM objects gets selected.

There is also a handler for IsSelectedChanged event of BusinessGroupVM. It ensures that whenever the BusinessGroupVM object is unselected, all its people are unselected also.

Now let us take a second look at BusinessGroupsVM class:

public class BusinessGroupsVM : SingleSelectionObservableCollection<businessgroupvm>
{
    IDisposable _behaviorsDisposable;
    public BusinessGroupsVM()
    {
        _behaviorsDisposable =
            this.AddBehavior
            (
                (businessGroup) => businessGroup.RemoveEvent += BusinessGroup_RemoveEvent,
                (businessGroup) => businessGroup.RemoveEvent -= BusinessGroup_RemoveEvent
            );
    }

    private void BusinessGroup_RemoveEvent(IRemovable businessGroupToRemove)
    {
        this.Remove((BusinessGroupVM)businessGroupToRemove);
    }
}  
</businessgroupvm>

As mentioned above, it is a SingleSelectionObservableCollection of BusingGroupVM objects. Such collection ensures that no more than one of the items in it is selected at a time.

It also contains functionality (implemented as a CollectionBehavior) that ensures that the item is removed from the collection whenever the item's RemoveEvent is fired.

Problems with the MultiConcernsTest Implementation

We find all the problems related to mixing of the concerns in our first sample.

Notice that a lot of functionality is duplicated, in particular, implementations of IRemovable and ISelectedItem interfaces are exactly the same within PersonVM and BusinessGroupVM classes. Additionally, the functionality in charge of removing a PersonVM object from BusinessGroupVM's People collection is the same as the functionality for removing a BusinessGroupVM object from BusinessGroupsVM collection.

The second major problem is that the concerns are all mixed up - single class e.g. PersonVM or BusinessGroupVM contains implementations for all of their concerns and this extra code dilutes the main purpose of the class (which is to contain and present useful data).

MultiConcernsBehaviorTest

Try running MultiConcernsBehaviorTest solution - you will see that it behaves exactly the same as the previous sample.

This sample is very similar to the first one, but it has two behavior based improvements. I factored out the functionality for removing an IRemovable from a collection into RemovableCollectionBehavior. Also I factored out the functionality responsible for unselecting a person when its business group is selected and selecting the business group when one of its people is selected into ParentChildSelectionBehavior.

Both behaviors are defined in NP.Utilities project under Behaviors folder.

Here is the code for RemovableCollectionBehavior:

public class RemovableCollectionBehavior
{
    IDisposable _behaviorDisposable = null;

    IEnumerable<IRemovable> _collection;
    public IEnumerable<IRemovable> TheCollection
    {
        get => _collection;

        set
        {
            if (ReferenceEquals(_collection, value))
                return;

            _collection = value;

            _behaviorDisposable =
                _collection?.AddBehavior
                (
                    (item) => item.RemoveEvent += Item_RemoveEvent,
                    (item) => item.RemoveEvent -= Item_RemoveEvent
                );
        }
    }

    private void Item_RemoveEvent(IRemovable itemToRemove)
    {
        (TheCollection as IList).Remove(itemToRemove);
    }
}  

Using AddBehavior extension method we assign the Item_RemoveEvent event handler to every item within the collection. The handler removes the passed item from the collection.

Code for ParentChildSelectionBehavior is also simple (though slightly more complex):

public class ParentChildSelectionBehavior<TParent, TChild>
    where TParent : class, ISelectableItem<TParent>
    where TChild : class, ISelectableItem<TChild>
{
    IDisposable _childrenBehaviorDisposable = null;

    TParent _parent;
    public TParent Parent
    {
        get => _parent;

        set
        {
            if (_parent.ObjEquals(value))
                return;

            if (_parent != null)
            {
                _parent.IsSelectedChanged -=
                    ParentChildSelectionBehavior_IsSelectedChanged;
            }

            _parent = value;

            if (_parent != null)
            {
                _parent.IsSelectedChanged +=
                    ParentChildSelectionBehavior_IsSelectedChanged;
            }
        }
    }

    ObservableCollection<TChild> _children;
    public ObservableCollection<TChild> Children
    {
        private get => _children;
        set
        {
            if (ReferenceEquals(_children, value))
                return;

            _children = value;

            _childrenBehaviorDisposable?.Dispose();
            _childrenBehaviorDisposable = _children.AddBehavior
             (
                child => child.IsSelectedChanged += Child_IsSelectedChanged,
                child => child.IsSelectedChanged -= Child_IsSelectedChanged
             );
        }
    }

    // unselect children if parent is unselected
    private void ParentChildSelectionBehavior_IsSelectedChanged(ISelectableItem<TParent> parent)
    {
        if (!parent.IsSelected)
        {
            foreach(TChild child in this.Children)
            {
                if (child.IsSelected)
                {
                    child.IsSelected = false;
                }
            }
        }
    }

    // select the parent if its child is selected. 
    private void Child_IsSelectedChanged(ISelectableItem<TChild> child)
    {
        if ((child.IsSelected) && (this.Parent != null))
        {
            this.Parent.IsSelected = true;
        }
    }
}  

Property Parent of this class should be assigned to the parent selectable item and property Children of this class should be assigned to a collection of ISelectedItem children.

RemovableCollectionBehavior is used in BusinessGroupVM class (to remove person objects from people collection) and BusinessGroupsVM (to remove BusinesGroupVM objects). ParentChildSelectionBehavior is used within BusinessgroupVM to control selection interaction between the BusinessGroupVM object and PersonVM objects within its people collection People.

You can see that the View Models code really became smaller and less confusing, e.g. the bottom of BusinessGroupVM file now looks:

ParentChildSelectionBehavior<BusinessGroupVM, PersonVM> _parentChildSelectionBehavior =
    new ParentChildSelectionBehavior<BusinessGroupVM, PersonVM>();

RemovableCollectionBehavior _removableCollectionBehavior =
    new RemovableCollectionBehavior();

public BusinessGroupVM()
{
    _removableCollectionBehavior.TheCollection = this.People;

    _parentChildSelectionBehavior.Parent = this;
    _parentChildSelectionBehavior.Children = this.People;
}  

instead of more than 30 lines in the previous sample. Moreover, the reuse of the code is better since the generic behaviors can also be used in other code and even in this sample, the RemovableCollectionBehavior is being reused in two places.

MultiConcernsRoxyTest - Roxy Implementation

Important Note

Unlike the previous samples, this demo project uses NP.Roxy nuget package from nuget.org.

Roxy Sample View Models

This is the main demo of the article - the one for which sake the article is written. It is located under MultiConcernsRoxyTest solution.

The sample essentially contains two code View Models (the rest are Roxy generated).

File PersonDataVM.cs contains IPersonDataVM interface and PersonDataVM class:

public interface IPersonDataVM
{
    string FirstName { get; set; }

    string LastName { get; set; }

    string FullName { get; }
}

public class PersonDataVM : IPersonDataVM
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName => FirstName + " " + LastName;
}  

The only reason we need a class - is because it has the code for FullName property.

File BusinessGroupDataVM.cs contains IBusinessGroup interface:

public interface IBusinessGroup
{
    string Name { get; set; }

    ObservableCollection<ISelectableRemovablePerson> People { get; }
}  

These is all 'hand-written' View Model functionality. You can see, that it only contains data without any other concerns.

There is also file RoxyModelAssembly.cs. It contains a number of very simple interfaces and a static class RoxyModelAssembler that assembles the full classes with the various concerns together. This file will be the focus of explanations in the rest of the section.

Brief Introduction to Roxy Functionality

In the future I plan to write more articles detailing Roxy functionality. Here, however, I only give a brief introduction to Roxy type generation in order to explain the contents of RoslynModelAssembly.cs file.

Objects of type ITypeConfig are central to creating Roxy generated type. They contain full information about the generated type. They can be modified, until their method ConfigurationCompleted() is called. After that they become 'Frozen' - no modification is allowed.

ITypeConfig objects are usually obtained by calling static Roxy method Core.FindOrCreateTypeConfig<...>(string className = null).

The generic type arguments to this method or (various its overloads) are of primary interest to us.

Most common overload of the method is Core.FindOrCreateTypeConfig<TImplementedInterface, TSuperClass, TWrappedInterface>(string className = null) where

  1. TImplementedInterface is the interface that the generated type implements. If it should not implement any interface, NP.Roxy.NoInterface type argument should be passed.
  2. TSuperClass is the class that the generated type will extend. If no class should be extended, NP.Roxy.NoClass type argument should be passed.
  3. TWrappedInterface is a special interface that defines the wrapped (mixin) objects that the generated class wraps. These objects provide the implementations for the generated class'es undefined or abstract or virtual methods and properties. In a sense these objects are the implementation inheritance 'superclasses' for the generated class.

className argument to the Core.FindOrCreateTypeConfig<...>(string className = null) method allows to specify the name of the generated class (should be, of course, unique within NP.Generated namespace). If no className is passed, Roxy will generate a default class name from the generic type arguments passed to the method.

 

Once the ITypeConfig objects are created and configured and their ConfigurationCompleted() method is called, the objects of the generated type can be obtained by calling static method Core.GetInstanceOfGeneratedType<T>(string className = null, params object[] args); where T can be a base class name or implemented interface name and args are the constructor arguments for the generated type - usually we use the default constructor and correspondingly args array is empty.

Inteface Implementations Used for Type Configuration

I am using the generic implementations of ISelectableItem<T> and IRemovable interfaces located under NP.Utilities project. The names of the corresponding classes are SelectableItem<T> and Removable. Here is SelectableItem<T> code:

public class SelectableItem<T> : VMBase, ISelectableItem<T>, INotifyPropertyChanged
    where T : ISelectableItem<T>
{
    bool _isSelected = false;

    [XmlIgnore]
    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }

        set
        {
            if (_isSelected == value)
                return;

            _isSelected = value;

            IsSelectedChanged?.Invoke(this);

            OnPropertyChanged(nameof(IsSelected));
        }
    }

    public event Action<ISelectableItem<T>> IsSelectedChanged;

    public void SelectItem()
    {
        this.IsSelected = true;
    }

    public void ToggleSelection()
    {
        this.IsSelected = !this.IsSelected;
    }
}  

You can see that SelectableItem also implements Notifiable concern and fires PropertyChanged event whenever its IsSelected property changes.

Here is Removable code:

public class Removable : IRemovable
{
    public event Action<iremovable> RemoveEvent;

    public void Remove()
    {
        RemoveEvent?.Invoke(this);
    }
}  
</iremovable>

Not much of a deal.

On top of SelectableItem and Removable classes, I am also using the RemovableCollectionBehavior and ParentChildSelectionBehavior classes explained as part of the previous sample.

RoxyModelAssembler Explained

Now we are ready to explain how the ITypeConfig objects are used within RoxyModelAssembler static class.

Static method RoxyModelAssembler.AssembleSelectableRemovablePerson() configures the PersonVM type that also implements ISelectableItem, IRemovable and INotifiablePropertyChanged interfaces:

// assembles the functionality for PersonVM with 
// selectable and removable capabilities
public static void AssembleSelectableRemovablePerson()
{
    // create type config
    ITypeConfig typeConfig =
        Core.FindOrCreateTypeConfig<ISelectableRemovablePerson, PersonDataVM, ISelectableRemovablePersonWrapper>();

    // set the first argument for PropertyChanged event 
    // to map into 'this' pointer
    typeConfig.SetEventArgThisIdx(nameof(INotifyPropertyChanged.PropertyChanged), 0);

    // complete the configuration
    typeConfig.ConfigurationCompleted();
}  

As you can see, it implements interface ISelectableRemovablePerson (also defined in the same file):

public interface ISelectableRemovableItem<T> : 
    ISelectableItem<T>, 
    IRemovable, 
    INotifyPropertyChanged
   where T : ISelectableItem<T>
{
}

// represents PersonVM - 
// the IPersonDataVM with Selectable and
// Removable functionality
public interface ISelectableRemovablePerson :
    IPersonDataVM, ISelectableRemovableItem<ISelectableRemovablePerson>
{

}  

(I also show the interface it extends - ISelectableRemovableItem)

ISelectableRemovablePerson interface inherits from another interface ISelectableRemovableItem which combines the ISelectableItem, IRemovable and INotifiablePropertyChanged interfaces. I factored ISelectableRemovableItem interface out, because it can also be used for BusingGroupVM functionality.

This interface also gets its data concern from IPersontDataVM interface.

Coming back to Core.FindOrCreateTypeConfig<ISelectableRemovablePerson, PersonDataVM, ISelectableRemovablePersonWrapper>(); method, the second generic type argument to it is PersonDataVM type, so the generated class will inherit from PersonDataVM and its property FullName will get the implementation from that class:

public string FullName => FirstName + " " + LastName;

The rest of the properties, events and methods of the generated type will be coming from the wrapped objects defined by ISelectableRemovablePersonWrapper interface:

// Wrapper interface for implementing 
// ISelectableRemovableItem interface
public interface ISelectableRemovableWrapper<T>
    where T : ISelectableItem<T>
{
    SelectableItem<T> Selectable { get; }

    Removable Removable { get; }
}


// the wrapper for 
// implementing ISelectableRemovablePerson
public interface ISelectableRemovablePersonWrapper :
    ISelectableRemovableWrapper<ISelectableRemovablePerson>
{
}  

(I also show the interface it extends - ISelectableRemovableWrapper)

This interface inherits from ISelectableRemovableWrapper. It contains two members Selectable of SelectableItem type and Removable of RemovableItem type. These members define the implementation for the Selectable and Removable concerns. ISelectableRemovableWrapper is factored out in order to reuse it for BusinessGroupVM generation.

Take a look at the static method RoxyModelAssembler.AssembleSelectableRemovableBusinessGroup() used to assemble BusinessGroupVM class, which combines business group data concern, Selectable, Removable, Notifiable concerns as well as the RemovableCollectionBehavior and ParentChildSelectionBehavior:

// Assembles the BusinessGroupVM functionality with 
// Selectable and Removable implementations and also 
// with ParentChildSelectionBehavior and RemovableCollectionBehavior.
public static void AssembleSelectableRemovableBusinessGroup()
{
    // get the type config object
    ITypeConfig typeConfig =
        Core.FindOrCreateTypeConfig<ISelectableRemovableBusinessGroup, ISelectableRemovableBusinessGroupWrapper>("BusinessGroupVM");

    // Adds the initialization of People collection to
    // an empty SingleSelectionObservableCollection collection within the constructor
    typeConfig.SetInit<SingleSelectionObservableCollection<ISelectableRemovablePerson>>(nameof(IBusinessGroup.People));

    // set the first argument for PropertyChanged event 
    // to map into 'this'
    typeConfig.SetEventArgThisIdx(nameof(INotifyPropertyChanged.PropertyChanged), 0);

    // maps Parent property of wrapped ParentChildSelectionBehavior
    // into 'this' field of the generated type. 
    typeConfig.SetThisMemberMap
    (
        nameof(ISelectableRemovableBusinessGroupWrapper.TheParentChildSelectionBehavior),
        nameof(ParentChildSelectionBehavior<ISelectableRemovableBusinessGroup, ISelectableRemovablePerson>.Parent)
    );

    // maps Children property of wrapped ParentChildCollectionBehavior
    // into People property of the generated type. 
    typeConfig.SetMemberMap
    (
        nameof(ISelectableRemovableBusinessGroupWrapper.TheParentChildSelectionBehavior),
        nameof(ParentChildSelectionBehavior<ISelectableRemovableBusinessGroup, ISelectableRemovablePerson>.Children),
        nameof(IBusinessGroup.People)
    );


    // maps TheCollection property of wrapped RemovableCollectionBehavior
    // into People property of the generated type. 
    typeConfig.SetMemberMap
    (
        nameof(ISelectableRemovableBusinessGroupWrapper.TheRemovableCollectionBehavior),
        nameof(RemovableCollectionBehavior.TheCollection),
        nameof(IBusinessGroup.People)
    );

    // specifies that the configuration is completed.
    typeConfig.ConfigurationCompleted();
}  

Note that a different overload of Core.FindOrCreateTypeConfig<...>(...) method is used the one that takes only two arguments - the interface to implement and the wrapper interface.

ISelectableRemovableBusinessGroup is the interface to implement:

// represents IBusinessGroup with Selectable and
// Removable functionality
public interface ISelectableRemovableBusinessGroup :
    IBusinessGroup,
    ISelectableRemovableItem<ISelectableRemovableBusinessGroup>
{

}  

Just like ISelectableRemovablePerson (discussed above) it inherits from ISelectableRemovableItem which provides Selectable, Removable and Notifiable type's concerns' usage sides.

The implementation sides for these concerns are specified by interface ISelectableRemovableBusinessGroupWrapper:

// Wrapper for the RemovableCollectionBehavior
public interface IRemovableCollectionBehaviorWrapper
{
    RemovableCollectionBehavior TheRemovableCollectionBehavior { get; }
}


// Wrapper for RemovableCollectionBehavior and 
// ParentChildSelectionBehavior
public interface ISelectableRemovableBusinessGroupWrapper :
    ISelectableRemovableWrapper<ISelectableRemovableBusinessGroup>,
    IRemovableCollectionBehaviorWrapper
{
    ParentChildSelectionBehavior<ISelectableRemovableBusinessGroup, ISelectableRemovablePerson> TheParentChildSelectionBehavior { get; }
}  

This interface defines the ParentChildSelectionBehavior. It also inherits from ISelectableRemovableWrapper interface that specifies the implementations for the Selectable, Removable and Notifiable concerns (as was discussed above). Finally it also inherits from IRemovableCollectionBehaviorWrapper that defines RemovableCollectionBehavior (IRemovableCollectionBehaviorWrapper interface is factored out because it is also needed for creating BusinessGroupsVM implementation).

The rest is pretty much documented within the code:

// Adds the initialization of People collection to
// an empty SingleSelectionObservableCollection collection within the constructor
typeConfig.SetInit<SingleSelectionObservableCollection<ISelectableRemovablePerson<<(nameof(IBusinessGroup.People));  

initializes the People collection to an empty SingleSelectionObservableCollection<ISelectableRemovablePerson> collection within the constructor.

There are also name mappings, e.g.

// maps Parent property of wrapped ParentChildSelectionBehavior
// into 'this' field of the generated type. 
typeConfig.SetThisMemberMap
(
    nameof(ISelectableRemovableBusinessGroupWrapper.TheParentChildSelectionBehavior),
    nameof(ParentChildSelectionBehavior<iselectableremovablebusinessgroup, iselectableremovableperson="">.Parent)
);  
</iselectableremovablebusinessgroup,>

maps this of the generated type into the ParentChildSelectionBehavior.Parent property.

Finally the static method to generate code for BusingsGroupsVM collection is RoxyModelAssembler.AssembleBusinessGroupsCollection():

// Assembles the BusinessGroupsVM collection. 
public static void AssembleBusinessGroupsCollection()
{
    ITypeConfig typeConfig =
        Core.FindOrCreateTypeConfig<NoInterface, SingleSelectionObservableCollection<ISelectableRemovableBusinessGroup>, IRemovableCollectionBehaviorWrapper>("BusinessGroupsVM");

    // maps TheCollection property of RemovableCollectionBehavior
    // into 'this' of the generated object. 
    typeConfig.SetThisMemberMap
    (
        nameof(IRemovableCollectionBehaviorWrapper.TheRemovableCollectionBehavior),
        nameof(RemovableCollectionBehavior.TheCollection)
    );

    typeConfig.ConfigurationCompleted();
}  

The ITypeConfig object does not specify implementation interface, but specifies the base class SingleSelectionObservableCollection<ISelectableRemovableBusinessGroup>. The wrapper interface is IRemovableCollectionBehaviorWrapper, which only contains the RemoveableCollectionBehavior. TheCollection property of the behavior maps into this of the generated object.

Code for Generating the Collection of Test Objects

Code for generating the test objects (the WPF Window's DataContext ) is different now since we cannot use the constructors for generated objects but have to use Roxy functionality for that.

This code is located within MainWindow.xaml.cs file within the constructor of MainWindow class:

public MainWindow()
{
    InitializeComponent();

    // create the generated types
    RoxyModelAssembler.AssembleSelectableRemovablePerson();
    RoxyModelAssembler.AssembleSelectableRemovableBusinessGroup();
    RoxyModelAssembler.AssembleBusinessGroupsCollection();

    // get the data context as BusinessGroupsVM type
    SingleSelectionObservableCollection<ISelectableRemovableBusinessGroup> dataContext =
        Core.GetInstanceOfGeneratedType<SingleSelectionObservableCollection<ISelectableRemovableBusinessGroup>>();

    // set the data context of the main window
    this.DataContext = dataContext;

   // create businessGroup1 as BusingGroupVM object
   ISelectableRemovableBusinessGroup businessGroup1 = 
        Core.GetInstanceOfGeneratedType<ISelectableRemovableBusinessGroup>();

    businessGroup1.Name = "Astrologists";

    // add businessGroup1 to the data context collection
    dataContext.Add(businessGroup1);

    // create person1 as PersonVM object
    ISelectableRemovablePerson person1 = Core.GetInstanceOfGeneratedType<ISelectableRemovablePerson>();

    //set properties
    person1.FirstName = "Joe";
    person1.LastName = "Doe";

    // add person1 object to businessGroup1
    businessGroup1.People.Add(person1);

    // create and add person2
    ISelectableRemovablePerson person2 = Core.GetInstanceOfGeneratedType<ISelectableRemovablePerson>();
    person2.FirstName = "Jane";
    person2.LastName = "Dane";
    businessGroup1.People.Add(person2);

    // create and add businessGroup2
    ISelectableRemovableBusinessGroup businessGroup2 =
        Core.GetInstanceOfGeneratedType<ISelectableRemovableBusinessGroup>();
    businessGroup2.Name = "Alchemists";
    dataContext.Add(businessGroup2);

    // create and add person3 
    ISelectableRemovablePerson person3 = Core.GetInstanceOfGeneratedType<ISelectableRemovablePerson>();
    person3.FirstName = "Michael";
    person3.LastName = "Mont";
    businessGroup2.People.Add(person3);

    // create and add person4
    ISelectableRemovablePerson person4 = Core.GetInstanceOfGeneratedType<ISelectableRemovablePerson>();
    person4.FirstName = "Michelle";
    person4.LastName = "Mitchell";
    businessGroup2.People.Add(person4);
}  

Note that we use Core.GetInstanceOfGeneratedType<...>() for creating the objects of generated types.

Conclusion

The main points of the article are

  1. Separation of concerns is essential behind building good, reusable and testable code.
  2. There are two types of inheritance - Usage and Implementation inheritance which losely map into C# or Java interface implementation and base class extensions.
  3. Every class within an application has multiple concerns to it.
  4. Every concern has Usage and Implementation side to it.
  5. Usage sides of multiple concerns can be easily merged using C# or Java language by using interface inheritance with ability to merge multiple members of the same name and signature into the same member.
  6. Implementation side of multiple concerns is not easy to merge by C# or Java or C++ language means alone. It is primarily to mitigate this drawback that I came up with Roxy IoC container and Code Generator.
  7. I advocate coming up with new language features and a special type of multiple inheritance or mixins that would improve merging of the Implementation sides of various concerns. This will allow to optimize compilation and provide better type safety features. The features of such 'multiple implementation inheritance' would be the following:
    1. It should be 'black box' inheritance where only specified functionality is being inherited - not the usual 'white box' inheritance where all protected and public functionality is being inherited.
    2. It should be easy to rename and map the 'superclass' functionality into a 'subclass' name.
    3. Various ways should be provided for merging multiple 'superclass' members that map into the same 'subclass' member.
    4. The same 'superclass' type can appear several times among the 'superclasses' of a type under different names. The members of such 'superclasses' can be mapped into different members of the 'subclass'

 

I plan to write more articles on Roxy describing its various features in-depth.

Acknowledgements

I would like to thank my dear wife Pazit for her help editing the article and making sure its content is clear and grammatically correct.

I have been using a great tool - Quick Diagram Tool for C# for generating the UML diagrams. My hat tip to its creator.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
-- There are no messages in this forum --