다음을 통해 공유


C#: Practical Poly


Introduction

Nope, sorry if you were looking for an article about clipping your pet parrot’s nails this is not about practical pet parrot podiatry.  We’re talking software and Polymorphism. 

This article has a dual purpose:

  1. Explain the way commands are passed from the menu in the Main Window to the discrete and decoupled usercontrols of the MVVM Entity Framework sample.
  2. Provide a concrete demonstration of OverRiding for Object Orientated friendly c#.

Most articles which touch on overriding are a bit light on where you’d really use this sort of technique and hopefully by seeing something working this’ll make it easier to imagine where you could use the approach in your own projects.

Intended Audience

The sample is in WPF so maybe you're thinking this is just for the WPF developer.  The intended audience is wider than one might first think. The code which does the commanding via messenger is scattered across several classes.  If you took that code it could pretty much be pasted into any other XAML based technology such as Windows Store Apps and Silverlight.  Fitting the buttons on a smartphone sized screen might mean some adaptation would be advisable though!

What about winforms developers?

The XAML friendly binding isn’t going to work but you could certainly send the messages from a click event in a menu usercontrol and subscribe to them in some presenter or code behind. MVVM Light can be used in windows forms and messaging would obviate the rather unpleasant subscribing to event handlers between usercontrols which would be the usual winforms approach.

Web?

You could use messenger on the server to decouple controllers but it’s over-riding which will be most directly relevant.

Raaark.  Pretty Polly. Pretty Polly,

The Requirement

Let’s start by outlining what’s going on. 

Keeping this short  enough so the non-WPF developer keeps on reading but still cover everything is quite a challenge.  Apologies in advance if that mix isn’t quite perfect.

The sample is a single window desktop application with a set of command buttons in a menu. What’s that you say Mr Picky?  It’s a menu like control to be precise.

The menu is in the (main) window.  Hosted by that window, but totally decoupled from it, are various UserControls which are presented one at a time.  You choose to work on People and the People UserControl is shown centre screen.  Choose Customers and the Customers UserControl is substituted.

These UserControls have similar functionality in that they are will be doing CRUD.  Click the Refresh button on the menu and whatever UserControl is in view will re read it's data from the database.  Click Delete and the UserControl currently in the view will delete a selected record.

Each UserControl is encapsulated and only the UserControl’s viewmodel knows it’s the particular one being shown and which record is selected.  The Menu and main window doesn’t care, it just sends a message to do a Command.  That message contains which of the 4 commands should be done.

From that outline those UserControls sound pretty similar.  As is often the case though, the devil lies in the detail.

Each deals with a different table in the database and these have different business rules.

For example, you cannot delete a customer who has any orders because you need to know who owes money and who bought what for the yearly accounts.  The bean counters and tax people have no sense of humour when you get those numbers wrong.

One could perhaps imagine formulating some list of pre-requisites and injecting them.  As it turns out that’s easier to imagine than implement.  Separate pieces of code for each of the different sub classes are often necessary and hence just separate pieces of code without any injection is often the most practical solution.

What Code to Share - and How

Since there is common functionality between these UserControls then we’re best keeping DRY and sharing some code somehow. 

The sample is developed to a passive view pattern – MVVM.   All the code which “does things” in a UserControl goes in it’s ViewModel.  It is these will respond to our commands.

For this particular aspect we have 4 different actions we need to handle Read; Update; Insert and Delete.

We want just the viewmodel which is in view to respond and that part of the behaviour will definitely be common to them all.

The list of common functionality is therefore:

  • Deciding it’s the ViewModel in view
  • Receiving the message
  • Deciding what sort of command it is
  • Calling the appropriate Method

The part which needs to be substituted is what happens in each of the 4 methods which will be called.

Those common parts go in a base ViewModel which can be inherited.

That will be calling some methods we want to make variable and this is the crux of this aspect of polymorphism.

In order for that base viewmodel to compile we need a method for each defined there.  We know it won’t do anything but the compiler doesn’t and it wants something solid to chew on when you invoke a method.

That can’t just be a regular method because we want to substitute something for it from the sub class.

To enable the inheriting Class to do something different we use the Virtual and OverRide keywords.

Over Riding

Some readers will already be familiar with virtual and override and can skip this section.  Others might find a brief reminder useful.
Some basic inheritance terminology.
A base class is a class which another inherits from.
A subclass is the class which has inherited from a base class.

Virtual methods are ones for which an inheriting class can substitute a different implementation.

An override method is such a method which provides a different implementation to the virtual in the base class.  They replace the functionality (inherited) from their base.

You don’t have to provide an over-ride in ever sub class and hence you could put default processing in the virtual base method and use that in most of the sub classes.  That would be in a somewhat different scenario to the sample though.

Technically, you can also use override an abstract or override but 2 levels of inheritance gets difficult to follow pretty quickly.  That’s a subject for another article.

What Override Does

Stating this is easy, understanding the implications is the tricky bit.

The virtual method from the base class is called but the override from the subclass has replaced it and is called instead.

Process Flow

Let’s consider what happens with one command as a ViewModel reacts to it.

CustomersViewModel is the viewmodel which “does stuff” with Customer data.

That inherits from CrudVMBase which is the base viewmodel discussed above.

It therefore inherits it’s methods which include HandleCommand.

When CustomerViewModel is acting on a command message it invokes HandleCommand.

When a delete command is received then HandleCommand has a switch statement which will call DeleteCurrent().  That virtual method in CrudVMBase is replaced by the DeleteCurrent() override in CustomersViewModel.  That checks to see whether there are any orders before it does any delete from Customers.

Obviously, this particular business logic here is pretty simple stuff and in a real world application the validation and processes might be much more complicated.

Maybe a customer which hasn’t had orders for over 5 years is considered “dead” and you’d copy old orders and the customer data onto some archive database before deleting them all off the OLTP database.

The Visual Aid

The delete button is pushed and CommandVM sends the message for a delete command type.
CustomersView is in the MainWindow so CustomersViewModel will act on that message.
HandleCommand is inherited from CrudVMBase and the CommandMessage is passed to it.
HandleCommand sees it's a delete and calls DeleteCurrent.
DeleteCurrent is over-ridden by CustomersViewModel and hence it is this version of the method which is invoked.
That knows which record to delete because the current item of the datagrid is bound from CustomersView and thatt will be deleted or a message shown.

The Code - Publish

At the risk of stating the obvious, this code is WPF so this part is separated out so readers who aren't so interested in WPF can skip it easily.  Head down towards the bottom of the article for the part specifically about overriding.

The "menu" is a TabControl in MainWindow which contains two TabItems.  In each is a ListView set to arrange it's items horizontally.  It is the DataTemplate of the ItemTemplate that produces a button for each item bound to it's ItemsSource.

Code - Menu Markup

Since there isn't a huge amount of code there the XAML below does the whole menu.  That includes the navigation buttons as well as the CRUD command buttons. By including all that it's hopefully less confusing.

<TabControl HorizontalAlignment="Left"> 
    <TabItem Header="Navigate" FontSize="10"> 
        <ListView ItemsSource="{Binding Views}" BorderBrush="Transparent" FontSize="12" FontWeight="Bold" 
                  ScrollViewer.CanContentScroll="False"
                  > 
            <ListView.ItemsPanel> 
                <ItemsPanelTemplate> 
                    <StackPanel Orientation="Horizontal"></StackPanel> 
                </ItemsPanelTemplate> 
            </ListView.ItemsPanel> 
            <ListView.ItemTemplate> 
                <DataTemplate> 
 
                        <Button Command="{Binding Navigate}" 
                                BorderThickness="0" Height="32" Width="100" 
                                Background="{StaticResource LightBrightGradientBrush}"
                                Foreground="{StaticResource DarkDullBrush}"
                                > 
                            <TextBlock Text="{Binding ViewDisplay}" 
                                   TextAlignment="Center" 
                                      
                                   /> 
                        </Button> 
                </DataTemplate> 
            </ListView.ItemTemplate> 
        </ListView> 
    </TabItem> 
    <TabItem Header="Edit"  FontSize="10"> 
        <ListView ItemsSource="{Binding Commands}" BorderBrush="Transparent" FontSize="12" FontWeight="Bold" 
                  ScrollViewer.CanContentScroll="False" 
                  > 
            <ListView.ItemsPanel> 
                <ItemsPanelTemplate> 
                    <StackPanel Orientation="Horizontal"/> 
                </ItemsPanelTemplate> 
            </ListView.ItemsPanel> 
            <ListView.ItemTemplate> 
                <DataTemplate> 
 
                        <Button Command="{Binding Send}" 
                                BorderThickness="0" 
                                Margin="0" Padding="0" 
                                > 
                            <Path Data="{Binding IconGeometry}" Stretch="Uniform" Fill="{StaticResource MidDullBrush}" Width="32" Height="32"/> 
                            <Button.ToolTip> 
                                <TextBlock Text="{Binding CommandDisplay}"/> 
                            </Button.ToolTip> 
                        </Button> 
 
                </DataTemplate> 
            </ListView.ItemTemplate> 
        </ListView> 
    </TabItem> 
</TabControl>

The second tabitem there presents the edit commands - binding to the Commands collection,  Each button will use a path to show an icon and binds to a "Send" command.

That Commands collection is an ObservableCollection<CommandVM> which is presented by MainWindowViewModel.

CommandVM

The code for that is:

public class  CommandVM
{
    public string  CommandDisplay { get; set; }
    public CommandMessage Message{ get; set; }
    public RelayCommand Send { get; private  set; }
    public Geometry IconGeometry { get; set; }
 
    private bool  canExecute = true;
    public bool  CanExecute
    {
        get
        {
            return canExecute = true;
        }
        set
        {
            canExecute= value;
            RaiseCanExecuteChanged();
        }
    }
 
    public CommandVM()
    {
        Send = new  RelayCommand(() => SendExecute());
    }
 
    private void  SendExecute()
    {
        Messenger.Default.Send<CommandMessage>(Message);
    }
    public void  RaiseCanExecuteChanged()
    {
        CommandManager.InvalidateRequerySuggested();
    }
}

There's that Send which is a RelayCommand sending a CommandMessage object using the pattern explained in more depth here.  In Brief, this is an implementation of the pub/sub pattern using MVVM Light messenger.  This part sends the message and the subscriber will receive and act on that message.

CommandMessage

This  is a very simple class.  It's the type of the class which is used by messenger to work out which message has been received,

public class  CommandMessage
{
    public CommandType Command { get; set; }
}
public enum  CommandType
{
    Insert,
    Edit,
    Delete,
    Commit,
    Refresh,
    Quit,
    None
}

As those CommandVM are instantiated, their message is set to a CommandMessage which has a matching CommandType Command. You probably worked that out in the time it took to read that code and see the enumeration there.

MainWindowViewModel

Showing you there's an observablecollection there would be a bit dull.  Let's focus on just the constructor of MainWindowViewModel.

public MainWindowViewModel()
{
    ObservableCollection<ViewVM> views = new  ObservableCollection<ViewVM>
    {
        new ViewVM{ ViewDisplay="Customers", ViewType = typeof(CustomersView), ViewModelType = typeof(CustomersViewModel)},
        new ViewVM{ ViewDisplay="Products", ViewType = typeof(ProductsView), ViewModelType = typeof(ProductsViewModel)}
    };
    Views = views;
    RaisePropertyChanged("Views");
    views[0].NavigateExecute();
 
    ObservableCollection<CommandVM> commands = new  ObservableCollection<CommandVM>
    {
        new CommandVM{ CommandDisplay="Insert", IconGeometry=Application.Current.Resources["InsertIcon"] as  Geometry , Message=new CommandMessage{ Command =CommandType.Insert}},
        new CommandVM{ CommandDisplay="Edit", IconGeometry=Application.Current.Resources["EditIcon"] as  Geometry , Message=new CommandMessage{ Command = CommandType.Edit}},
        new CommandVM{ CommandDisplay="Delete", IconGeometry=Application.Current.Resources["DeleteIcon"] as  Geometry , Message=new CommandMessage{ Command = CommandType.Delete}},
       // new CommandVM{ CommandDisplay="Commit", IconGeometry=Application.Current.Resources["SaveIcon"] as Geometry , Message=new CommandMessage{ Command = CommandType.Commit}},
        new CommandVM{ CommandDisplay="Refresh", IconGeometry=Application.Current.Resources["RefreshIcon"] as  Geometry , Message=new CommandMessage{ Command = CommandType.Refresh}}
 
    };
    Commands = commands;
    RaisePropertyChanged("Commands");
}

There you can see the CommandType set up as mentioned above.  The icons were mentioned as well. These are Geometries in a resource dictionary merged into Resources by app.xaml. Application.Current. Resources is a sort of hash table where those merged resources go and this code dips in to grab those.

We've now seen enough to understand the publish part of the pub/sub pattern.  A button is bound to a CommandVM whose Send command will send a MVVM Light message.
Let's more on to the Subscribe part which is where that OverRiding business is going to be used.

The Code - Subscribe

As explained previously, the code which is going to receive that message and do things comes from a base ViewModel.

CrudVMBase

As the name suggests this is the base viewmodel for CRUD orientated UserControls. There are only two of these in the sample, but it's the first in a series.

public class  CrudVMBase : NotifyUIBase 
{ 
    protected SalesContext db = new SalesContext(); 
 
    protected void  HandleCommand(CommandMessage action) 
    { 
        if (isCurrentView) 
        { 
            switch (action.Command) 
            { 
                case CommandType.Insert: 
                    break; 
                case CommandType.Edit: 
                    break; 
                case CommandType.Delete: 
                    DeleteCurrent(); 
                    break; 
                case CommandType.Commit: 
                    CommitUpdates(); 
                    break; 
                case CommandType.Refresh: 
                    RefreshData(); 
                    break; 
                default: 
                    break; 
            } 
        } 
    } 
    private Visibility throbberVisible = Visibility.Visible;  
    public Visibility ThrobberVisible 
    { 
        get { return throbberVisible; } 
        set
        { 
            throbberVisible = value; 
            RaisePropertyChanged(); 
        } 
    } 
    private string  errorMessage; 
 
    public string  ErrorMessage 
    { 
        get { return errorMessage; } 
        set 
        {  
            errorMessage = value; 
            RaisePropertyChanged(); 
        } 
    } 
      
    protected virtual  void CommitUpdates() 
    { 
    } 
    protected virtual  void DeleteCurrent() 
    { 
    } 
    protected virtual  void RefreshData() 
    { 
        GetData(); 
        Messenger.Default.Send<UserMessage>(new UserMessage {Message="Data Refreshed" }); 
    } 
    protected virtual  void GetData() 
    { 
    } 
    protected CrudVMBase() 
    { 
        GetData(); 
        Messenger.Default.Register<CommandMessage>(this, (action) => HandleCommand(action)); 
        Messenger.Default.Register<NavigateMessage>(this, (action) => CurrentUserControl(action)); 
    } 
    protected bool  isCurrentView = false; 
    private void  CurrentUserControl(NavigateMessage nm) 
    { 
        if (this.GetType() == nm.ViewModelType) 
        { 
            isCurrentView = true; 
        } 
        else
        { 
            isCurrentView = false; 
        } 
    } 
}

Some of the code there - like controlling visibility of the throbber - isn't within scope so let's gloss over a few things.
The MVVM Light messages are subscribed to in the constructor and wired up to methods.  The one which is of particular interest to us at the moment is HandleCommand.  That lives up to it's name and calls the appropriate method depending on the CommandType received.

HandleCommand() is a regular protected method because the functionality is common to all subclasses - nothing is going to replace that.
Looking at that you can see it's invoking virtual methods - such as DeleteCurrent().  Those have no code in them because they are most definitely going to be replaced using override methods in the subclasses inheriting from CrudVMBase.

CustomersViewModel

If you've  seen this thing running it's pretty obvious, but just in case you came straight to this it's probably best to mention what data is going to be edited.   The two entities you can edit in the sample are Customers and Products.  The UserControls which are used to work on each inherit from CrudVMBase and override the 4 CRUD methods.
Let's focus on just 2 of these methods in CustomersViewmodel rather than go through everything in both.

protected override  void DeleteCurrent() 
{ 
    UserMessage msg = new  UserMessage(); 
    if (SelectedCustomer !=null) 
    { 
        int NumOrders = NumberOfOrders(); 
        if (NumOrders > 0) 
        { 
            msg.Message = string.Format("Cannot delete - there are {0} Orders for this Customer", NumOrders); 
        } 
        else
        { 
            db.Customers.Remove(SelectedCustomer.TheCustomer); 
            Customers.Remove(SelectedCustomer); 
            RaisePropertyChanged("Customers"); 
            msg.Message = "Deleted";  
        } 
    } 
    else
    { 
        msg.Message = "No Customer selected to delete"; 
    } 
    Messenger.Default.Send<UserMessage>(msg); 
} 
 
.............. snip ............
protected async override void  GetData() 
{ 
    ThrobberVisible = Visibility.Visible; 
    ObservableCollection<CustomerVM> _customers = new  ObservableCollection<CustomerVM>(); 
    var customers = await (from c in  db.Customers 
                            orderby c.CustomerName 
                            select c).ToListAsync(); 
    //  await Task.Delay(9000); 
    foreach (Customer cust in customers) 
    { 
        _customers.Add(new CustomerVM { IsNew = false, TheCustomer = cust }); 
    } 
    Customers = _customers; 
    RaisePropertyChanged("Customers"); 
    ThrobberVisible = Visibility.Collapsed; 
}

Up in that DeleteCurrent() we can see a check for related orders.  Should the Customer have any orders a message will be displayed ( by MainWindow ) instead of deleting the record.  The processing done is then a simple delete.  Complicated business rules are fairly common on deletes and would be implemented or workflow started there. Summaries could be re-calculated, people informed via email etc.

GetData() is the other method and you will notice this is marked async.  All the viewmodels will read their data asynchronously but you could conceivably  have a requirement where some processes were long and complicated and needed async processing and some didn't.

As an alternative, you could perhaps consider injecting a specific table to delete a record from.  That could work if all your deletes were very simple and just deleted a record.
Reading data is a bit more complicated.
As you look at GetData notice that even this pretty simple piece of code has quite a number of things which would vary if you tried to make some generic reading process.  Although this is definitely possible it'd be quite a bit of work.

Summary

We saw how it is practical to totally decouple the menu in an application from various usercontrols.  
Why bother?
Decoupling code is good since it makes it easier to refactor one or more parts.  It also makes automated testing much more practical.  There are no dependencies to have to mock or stub.

We also saw how it is fairly easy to have a base class method call code which is in a subclass ( inheriting from that ) using override of a virtual method.

Alternative to Over Riding

The sample has a fixed requirement  with only a few subclasses.  Each of the entities have static processing.  When you instead have variable processing then OverRiding might not suit so well.

In which case you could use injection instead.  That approach is rather outside the scope of this article but briefly:

You could use a public List<Action> or constructor parameter to inject a variable set of processes.