Press "Enter" to skip to content

Lesson 04.4: Improving the World – Inheritance and INotifyPropertyChanged

In this lesson, we will do some refactoring – cleanup, and minor improvements to the code we wrote over the last few lessons.

This is a common practice. Many times, you need to try several ideas, to add in new features. Once you have them working, it’s a good practice to review the code, and see if there are any ways to improve it.

These changes will focus on improvements with the code to handle INotifyPropertyChanged.

Improve the property name parameter, in the notification

Step 1: Open GameSession.cs.

Inside the “set” code, for CurrentLocation, we have these lines:

OnPropertyChanged("CurrentLocation");
OnPropertyChanged("HasLocationToNorth");
OnPropertyChanged("HasLocationToEast");
OnPropertyChanged("HasLocationToWest");
OnPropertyChanged("HasLocationToSouth");

This uses string values to pass the name of the property that changed. This works, but might cause a problem later.

If we ever rename a property, the program will still compile and run. However, the UI won’t update – because the changed property has a new name.

We’ll change the code to use the nameof() function. This function takes the property as a parameter. If we ever rename the property, the parameter will update (if we use Visual Studio refactoring), or report an error when we compile the program (letting us know about the problem quickly).

Change these lines to:

OnPropertyChanged(nameof(CurrentLocation));
OnPropertyChanged(nameof(HasLocationToNorth));
OnPropertyChanged(nameof(HasLocationToEast));
OnPropertyChanged(nameof(HasLocationToWest));
OnPropertyChanged(nameof(HasLocationToSouth));

Now, the parameter we pass to OnPropertyChanged will be updated, if we ever rename the property by using the “Rename” refactoring menu option.

Step 2: Open the Player class, and make the same changes where it uses OnPropertyChanged.

So, these lines:

OnPropertyChanged("Name");
OnPropertyChanged("CharacterClass");
OnPropertyChanged("HitPoints");
OnPropertyChanged("ExperiencePoints");
OnPropertyChanged("Level");
OnPropertyChanged("Gold");

Will now look like this:

OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(CharacterClass));
OnPropertyChanged(nameof(HitPoints));
OnPropertyChanged(nameof(ExperiencePoints));
OnPropertyChanged(nameof(Level));
OnPropertyChanged(nameof(Gold));

Eliminate duplicated code, using inheritance

You might have noticed that we have duplicated code to handle INotifyPropertyChanged in the Player and GameSession classes.

You normally don’t want to have duplicated code. If you do, and you need to make a change, you need to remember all the places where that code exists – and make the change in every place.

It’s better to have the code in one place, and have all the classes use it from that one place. If you ever need to make a change, you make it in the one place, and it’s updated for every class that uses it.

One way to share code is to create a “base” class, and have your other classes “inherit” from that base class. That is what we’ll do in this lesson.

Step 1: In the Engine project, create a new class named “BaseNotificationClass.cs”

Make the class public, and have it implement INotifyPropertyChanged. Add the PropertyChanged event and the OnPropertyChanged() function. The code should look like this:

using System.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Engine
{
    public class BaseNotificationClass : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

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

Step 2: Edit the Player and GameSession classes.

Currently, we have “INotifyPropertyChanged” after the lines where we start the Player and GameSession classes.

Because INotifyPropertyChanged is an interface, that means the class needs to implement the interface – have the properties, events, and functions defined in the interface. A class can implement multiple interfaces. To do that, we would have added a comma after INotifyPropertyChanged and listed another interface to implement.

Now, we have “BaseNotificationClass” available, which is a class – not an interface.

We will have the Player and GameSession classes “inherit” from BaseNotificationClass.

With inheritance, we would say that “Player” is a “child” of “BaseNotificationClass” – or that “BaseNotificationClass” is the “parent” of “Player”. A class can only inherit from one class in C# – it can only have one “parent”. However, a parent class can have its own parent. You can have many “generations” of inheritance. Although, you don’t really want to use too many. It can become difficult to manage.

A child class has access to the events, properties, and functions in its parent class – if they are public, internal, or protected. So, Player and GameSession use the PropertyChanged event and OnPropertyChanged function in BaseNotificationClass, instead of having their own.

BaseNotificationClass, and its "child" classes - Player and GameSession

To have the Player and GameSession classes inherit from BaseNotificationClass, replace the “INotifyPropertyChanged” with “BaseNotificationClass”. Then, remove the PropertyChanged event and the OnPropertyChanged function.

Player.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Engine.Models
{
    public class Player : BaseNotificationClass
    {
        private string _name;
        private string _characterClass;
        private int _hitPoints;
        private int _experiencePoints;
        private int _level;
        private int _gold;

        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged(nameof(Name));
            }
        }

        public string CharacterClass
        {
            get { return _characterClass; }
            set
            {
                _characterClass = value;
                OnPropertyChanged(nameof(CharacterClass));
            }
        }

        public int HitPoints
        {
            get { return _hitPoints; }
            set
            {
                _hitPoints = value;
                OnPropertyChanged(nameof(HitPoints));
            }
        }

        public int ExperiencePoints
        {
            get { return _experiencePoints; }
            set
            {
                _experiencePoints = value;
                OnPropertyChanged(nameof(ExperiencePoints));
            }
        }

        public int Level
        {
            get { return _level; }
            set
            {
                _level = value;
                OnPropertyChanged(nameof(Level));
            }
        }

        public int Gold
        {
            get { return _gold; }
            set
            {
                _gold = value;
                OnPropertyChanged(nameof(Gold));
            }
        }
    }
}
GameSession.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine.Models;
using Engine.Factories;

namespace Engine.ViewModels
{
    public class GameSession : BaseNotificationClass
    {
        private Location _currentLocation;

        public World CurrentWorld { get; set; }
        public Player CurrentPlayer { get; set; }
        public Location CurrentLocation
        {
            get {  return _currentLocation;}
            set
            {
                _currentLocation = value;

                OnPropertyChanged(nameof(CurrentLocation));
                OnPropertyChanged(nameof(HasLocationToNorth));
                OnPropertyChanged(nameof(HasLocationToEast));
                OnPropertyChanged(nameof(HasLocationToWest));
                OnPropertyChanged(nameof(HasLocationToSouth));
            }
        }

        public bool HasLocationToNorth
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1) != null;
            }
        }

        public bool HasLocationToEast
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate) != null;
            }
        }

        public bool HasLocationToSouth
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1) != null;
            }
        }

        public bool HasLocationToWest
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate) != null;
            }
        }

        public GameSession()
        {
            CurrentPlayer = new Player();
            CurrentPlayer.Name = "Scott";
            CurrentPlayer.CharacterClass = "Fighter";
            CurrentPlayer.HitPoints = 10;
            CurrentPlayer.Gold = 1000000;
            CurrentPlayer.ExperiencePoints = 0;
            CurrentPlayer.Level = 1;

            WorldFactory factory = new WorldFactory();
            CurrentWorld = factory.CreateWorld();

            CurrentLocation = CurrentWorld.LocationAt(0, 0);
        }

        public void MoveNorth()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1);
        }

        public void MoveEast()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate);
        }

        public void MoveSouth()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1);
        }

        public void MoveWest()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate);
        }
    }
}

When we create more classes (Models and ViewModels) that need to notify the UI of changes, they will also inherit from BaseNotificationClass.

Leave a Reply

Your email address will not be published. Required fields are marked *