Does your View-model Violate Single Responsibility

Intro

The majority of us are unknowingly violating the Single Responsibility Principle when constructing our view-models. The XAML below consists of three buttons that when pressed will show a description relative to each button. There is also a clear button to clear all description fields.

XAML:

<Window .
        . . .
        xmlns:local="clr-namespace:WpfApplication2">

    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Height" Value="40" />
            <Setter Property="Margin" Value="5" />
        </Style>
    </Window.Resources>
    
    <Window.DataContext>
        <local:ViewModel />    
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Button Grid.Column="0" Grid.Row="0" Content="Command1" Command="{Binding Command1}" />
        <TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding Description1}" />

        <Button Grid.Column="1" Grid.Row="0" Content="Command2" Command="{Binding Command2}" />
        <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Description2}" />

        <Button Grid.Column="2" Grid.Row="0" Content="Command3" Command="{Binding Command3}" />
        <TextBlock Grid.Column="2" Grid.Row="1" Text="{Binding Description3}" />
        
        <Button Grid.Column="1" Grid.Row="2" Content="Clear" Command="{Binding ClearCommand}" />
    </Grid>
</Window>

Now Take the following view-model:

    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ViewModel()
        {
            Command1 = new DelegateCommand(OnDescription1Requested);
            Command2 = new DelegateCommand(OnDescription2Requested);
            Command3 = new DelegateCommand(OnDescription3Requested);

            ClearCommand = new DelegateCommand(obj => Description1 = Description2 = Description3 = string.Empty);
        }

        string _description1 = null;
        public string Description1
        {
            get { return _description1; }
            set
            {
                if (_description1 != value)
                {
                    _description1 = value;
                    OnNotifyPropertyChanged();
                }
            }
        }

        string _description2 = null;
        public string Description2
        {
            get { return _description2; }
            set
            {
                if (_description2 != value)
                {
                    _description2 = value;
                    OnNotifyPropertyChanged();
                }
            }
        }

        string _description3 = null;
        public string Description3
        {
            get { return _description3; }
            set
            {
                if (_description3 != value)
                {
                    _description3 = value;
                    OnNotifyPropertyChanged();
                }
            }
        }

        public ICommand Command1 { get; private set; }
        public ICommand Command2 { get; private set; }
        public ICommand Command3 { get; private set; }
        public ICommand ClearCommand { get; private set; }

        private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        
 
        private void OnDescription1Requested(object obj)
        {
            // Some setup logic before property value is assigned. . .

            // Then assign property value
            Description1 = "some text for description1";
        }

        private void OnDescription2Requested(object obj)
        {
            // Some setup logic before property value is assigned. . .

            // Then assign property value
            Description2 = "some text for description2";
        }

        private void OnDescription3Requested(object obj)
        {
            // Some setup logic before property value is assigned. . .

            // Then assign property value
            Description3 = "some text for description3";
        }
    }

This view-model handles state and accepts commands. So what’s wrong with this view-model? Maybe nothing. It provides the necessities that our view requires to present itself and to submit requests. But look deeper into this view-model. What is its purpose? What is it severely focused on or in other words, how well is it focused?

This view-model is handling two responsibilities. It’s reflecting state and it’s also managing requests. That alone violates the Single Responsibility Principle which is meant to protect us from maintenance concerns. How ironic that WPF’s core design principle is to separate presentation from behavior. However, when practicing MVVM, we religiously bloat our view-models to do the exact opposite and end up coupling behaviors with states without giving it a second thought.

An Alternative Approach

What if we could create a mechanism for separating responsibilities of state and behavior? Specifically, why can’t we create a responder to handle view requests and use our view-model to only reflect the state that our views require for presenting? Then, our actual view-model could be a lot cleaner. Thus, we could offload behaviors of our view-model to a more focused class whose sole responsibility is to respond to commands. Now that sounds like a much better solution to what we have currently.

I acknowledge that some people may say that my recommendation adds unnecessary complexity and that adding this complexity does not produce any noticeable value. Their assumptions are valid. At least until several people begin to maintain that same view-model. It is then that the view-model’s complexity skyrockets out of control and slows down developers from making progress. The view-model’s initial purpose becomes obscured with many responsibilities and results in the view-model appearing more like congress (slow, complex, and in debt).
Creating a ViewModelBase

Let’s start by creating a base class that will manage databinding notifications.

    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Adding a Responder

Next, let’s add a responder . The responder’s core responsibility is to respond to commands. We’ll also use a pub/sub mechanism to perform messaging agnostically between client and server. In this case we use the “Bizmonger.Patterns” package that can be downloaded from Nuget. It provides a MessageBus to accomplish messaging. In this class we will publish messages after we have processed a command.

    public class Responder : ViewModelBase
    {
        public Responder()
        {
            Command1 = new DelegateCommand(OnDescription1Requested);
            Command2 = new DelegateCommand(OnDescription2Requested);
            Command3 = new DelegateCommand(OnDescription3Requested);

            ClearCommand = new DelegateCommand(obj => MessageBus.Instance.Publish("clear_descriptions"));
        }

        public ICommand Command1 { get; private set; }
        public ICommand Command2 { get; private set; }
        public ICommand Command3 { get; private set; }
        public ICommand ClearCommand { get; private set; }

        private void OnDescription1Requested(object obj)
        {
            // Some setup logic before property value is assigned. . .

            // Then assign property value
            MessageBus.Instance.Publish("Description1", "some text for description1");
        }

        private void OnDescription2Requested(object obj)
        {
            // Some setup logic before property value is assigned. . .

            // Then assign property value
            MessageBus.Instance.Publish("Description2", "some text for description2");
        }

        private void OnDescription3Requested(object obj)
        {
            // Some setup logic before property value is assigned. . .

            // Then assign property value
            MessageBus.Instance.Publish("Description3", "some text for description3");
        }
    }

Update ViewModel

Let’s remove all command references from this view-model and just let this class focus on state. We will use subscriptions for information updates in our constructor using the MessageBus.

    public class ViewModel : ViewModelBase
    {
        public ViewModel()
        {
            MessageBus.Instance.Subscribe("clear_descriptions", 
                obj => Description1 = Description2 = Description3 = string.Empty);

            MessageBus.Instance.Subscribe("Description1", obj => Description1 = obj as string);
            MessageBus.Instance.Subscribe("Description2", obj => Description2 = obj as string);
            MessageBus.Instance.Subscribe("Description3", obj => Description3 = obj as string);
        }

        string _description1 = null;
        public string Description1
        {
            get { return _description1; }
            set
            {
                if (_description1 != value)
                {
                    _description1 = value;
                    OnNotifyPropertyChanged();
                }
            }
        }

        string _description2 = null;
        public string Description2
        {
            get { return _description2; }
            set
            {
                if (_description2 != value)
                {
                    _description2 = value;
                    OnNotifyPropertyChanged();
                }
            }
        }

        string _description3 = null;
        public string Description3
        {
            get { return _description3; }
            set
            {
                if (_description3 != value)
                {
                    _description3 = value;
                    OnNotifyPropertyChanged();
                }
            }
        }
    }

Update XAML

Let’s add a responder as a resource and use that as our data context for all commands within our XAML.

<Window .
        .
        xmlns:local="clr-namespace:WpfApplication2">

    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Height" Value="40" />
            <Setter Property="Margin" Value="5" />
        </Style>
        
        <local:Responder x:Key="Responder " />
    </Window.Resources>
    
    <Window.DataContext>
        <local:ViewModel />    
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Button DataContext="{StaticResource Responder }" Grid.Column="0" Grid.Row="0" Content="Command1" Command="{Binding Command1}" />
        <TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding Description1}" />

        <Button DataContext="{StaticResource Responder }" Grid.Column="1" Grid.Row="0" Content="Command2" Command="{Binding Command2}" />
        <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Description2}" />

        <Button DataContext="{StaticResource Responder }" Grid.Column="2" Grid.Row="0" Content="Command3" Command="{Binding Command3}" />
        <TextBlock Grid.Column="2" Grid.Row="1" Text="{Binding Description3}" />
        
        <Button DataContext="{StaticResource Responder }" Grid.Column="1" Grid.Row="2" Content="Clear" Command="{Binding ClearCommand}" />
    </Grid>
</Window>

Conclusion

In conclusion, the majority of us are unknowingly violating the Single Responsibility Principle when constructing our view-models. Our view-models are usually handling two responsibilities. They’re reflecting state and also managing client requests. That alone violates the Single Responsibility Principle which is meant to protect us from maintenance concerns. When practicing MVVM, we unconsciously bloat our view-models with multiple responsibilities which results in coupling behaviors with state information which WPF is designed to protect ourselves against. Instead of accruing this technical debt, we can separate these concerns by creating a separate mechanism for command response and use our view-models to only reflect state for our views to present.

NOTE:

Scott Nimrod is fascinated with Software Craftsmanship.

He loves responding to feedback and encourages people to share his articles.

He can be reached at scott.nimrod @ bizmonger.net

One Reply to “Does your View-model Violate Single Responsibility”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: