We have seen that in a WPF application, it is essential for us to have an implementation of the INotifyPropertyChanged interface in our View Model base class. Likewise, we will also need a similar implementation in our Data Model base class. Remember that when Data Models are mentioned here, we are discussing the business Model classes that are combined with the View Model properties and functionality from the second application structure example in Chapter 1, A Smarter Way of Working with WPF.
All of these DataModel classes will need to extend their base class because they will all need to have access to its INotifyPropertyChanged implementation. As we progress through the chapters in this book, we will see more and more reasons why we need separate base classes for our Data Models and View Models. For example, let's imagine that we want to provide these Data Models with some simple auditing properties and investigate what our base class might look like:
using System;
using System.ComponentModel; using System.Runtime.CompilerServices;
namespace CompanyName.ApplicationName.DataModels
{
public class BaseDataModel : INotifyPropertyChanged
{
private DateTime createdOn;
private DateTime? updatedOn;
private User createdBy, updatedBy;
public DateTime CreatedOn
{
get { return createdOn; }
set { createdOn = value; NotifyPropertyChanged(); }
}
public User CreatedBy
{
get { return createdBy; }
set { createdBy = value; NotifyPropertyChanged(); }
}
public DateTime? UpdatedOn
{
get { return updatedOn; }
set { updatedOn = value; NotifyPropertyChanged(); }
}
public User UpdatedBy
{
get { return updatedBy; }
set { updatedBy = value; NotifyPropertyChanged(); }
}
#region INotifyPropertyChanged Members
...
#endregion
}
}
Here, we see our auditing properties, along with the hidden INotifyPropertyChanged implementation that we saw earlier. For now, let's keep the implementation the same as that of the BaseViewModel class. Note that using this particular base class would result in all derived classes getting access to these properties, whether they needed them or not.
We might then decide to declare another base class, so that we can have one that provides access to our implementation of the INotifyPropertyChanged interface and one that extends that base class and adds the new auditable properties shown earlier. In this way, all derived classes can make use of the INotifyPropertyChanged interface implementation and the classes that require the auditable properties as well can be derived from the second base class:
For this basic example, we seem to have solved our problem. If these auditable properties were the only properties that we wanted to provide to our derived classes, then this would not be such a bad situation. However, an average a framework will typically provide far more than this.
Let's now imagine that we wanted to provide some basic undo capability. We'll see an example of this later in this chapter, but for now we'll keep this simple. Without actually specifying the required members of this new base class, let's just think about this first.
Now we have a situation where we already have two different base classes and we want to provide some further functionality. Where should we declare our new properties? We could derive from either one, or indirectly, from both of the existing base classes, as shown in the following diagram, in order to create this new synchronizable base class:
So now, we could have four different base classes that the developers, that use our framework could extend. There could be some confusion as to exactly which base class they need to extend, but overall, this situation is still just about manageable. However, imagine if we want to provide some additional properties or functionality in one or more levels of base class.
In order to enable every combination of functionality from these base classes, we could end up with as many as eight separate base classes. Each additional level of functionality that we provide will either double the total number of base classes that we have, or mean that the developers sometimes have to derive from a base class with functionality or properties that they do not require. Now that we have uncovered a potential problem of utilizing base classes, let's see if declaring interfaces can help with this situation.