Sunday 11 October 2015

Faux Inheritance

C# will not allow you to inherit from multiple base classes; you can only inherit from one class, but your class can implement multiple interfaces. Let's take a look at our previous examples of an IStudent interface, Person Class and our Student Class that inherits Person and implements IStudent.


namespace pav.multipleInterfaces;

class Program
{
    interface IStudent
    {
        int StudentNumber { get; }
        IList<decimal> Grades { get; set; }
        decimal CalculateGPA();
    }

    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime Birthdate { get; set; }
        public string FullName { get { return $"{FirstName} {LastName}"; } }

        public Person() {
            this.FirstName = "";
            this.LastName = "";
        }

        public Person(string FirstName, string LastName) : this() {
            this.FirstName = FirstName;
            this.LastName = LastName;
        }

        public Person(string FirstName, string LastName, DateTime Birthdate)
            : this(FirstName, LastName) {
            this.Birthdate = Birthdate;
        }

        public string WriteValue() { return $"{FullName}"; }
    }

    class Student : Person, IStudent
    {
        static int RunntingStudentNumber = 0;

        public IList<decimal> Grades { get; set; }
        public int StudentNumber { get; } = RunntingStudentNumber++;

        public Student(string FirstName, string LastName, DateTime Birthdate) : base(FirstName, LastName, Birthdate)
        {
            this.Grades = new List<decimal>();
        }

        public Student()
        {
            this.Grades = new List<decimal>();
        }

        public decimal CalculateGPA()
        {
            decimal total = 0;
            foreach (var g in Grades)
                total += g;
            return total / Grades.Count;
        }
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}


now let's create an Employee class which inherits from person,


    class Employee : Person
    {
        static int runningId = 0;
        public int Id { get; private set; } = runningId++;
       
        public Employee() : base() { }
        public Employee(string FirstName, string LastName)
            : base(FirstName, LastName) { }
        public Employee(string FirstName, string LastName, DateTime Birthdate)
            : base(FirstName, LastName, Birthdate) { }

        public new string WriteValue() { return $"{Id}: {base.WriteValue()}"; }

    }


and now we're ready to create our TeachingAssistant class, a class that is both an employee and a student. The reason we organized our code in this fashion is because we can have employees, students and teaching assistants, they all inherit from Person, and all teaching assistants are both employees and students, but not all employees are students and vice versa.


    class TeachingAssistant : Employee, IStudent
    {
        static int RunntingStudentNumber = 0;
        public IList<decimal> Grades { get; set; }
        public int StudentNumber { get; } = RunntingStudentNumber++;
        public decimal CalculateGPA()
        {
            decimal total = 0;
            foreach (var g in Grades)
                total += g;
            return total / Grades.Count;
        }
    }


now is this ideal? no it's not because we still had to rewrite the implementation for IStudent inside of the Teaching assistant class, but at least this way we can at least ensure that the public members we expect are present. 

An alternative option is to create an internal instance of a studentLogic class which would implement the IStudent interface, then either instantiate that class within our teaching Assistant and Student classes and expose the logic via the IStudent members of the respective classes. Such an approach could look like the following.

namespace pav.multipleInterfaces;
class Program
{
    public interface IStudent
    {
        int StudentNumber { get; }
        IList<decimal> Grades { get; set; }
        decimal CalculateGPA();
    }

    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime Birthdate { get; set; }
        public string FullName { get { return $"{FirstName} {LastName}"; } }
        public Person()
        {
            this.FirstName = "";
            this.LastName = "";
        }
        public Person(string FirstName, string LastName) : this()
        {
            this.FirstName = FirstName;
            this.LastName = LastName;
        }
        public Person(string FirstName, string LastName, DateTime Birthdate)
            : this(FirstName, LastName)
        {
            this.Birthdate = Birthdate;
        }

        public string WriteValue() { return $"{FullName}"; }
    }

    internal class StudentLogic : IStudent
    {
        static int RunntingStudentNumber = 0;

        public IList<decimal> Grades { get; set; }
        public int StudentNumber { get; } = RunntingStudentNumber++;

        public StudentLogic()
        {
            this.Grades = new List<decimal>();
        }

        public decimal CalculateGPA()
        {
            decimal total = 0;
            foreach (var g in Grades)
                total += g;
            return total / Grades.Count;
        }
    }

    class Student : Person, IStudent
    {
        private IStudent _student;

        public IList<decimal> Grades
        {
            get
            {
                return _student.Grades;
            }
            set
            {
                _student.Grades = value;
            }
        }

        public int StudentNumber
        {
            get
            {
                return _student.StudentNumber;
            }
        }

        public Student(string FirstName, string LastName, DateTime Birthdate) :  base(FirstName, LastName, Birthdate)
        {
            this._student =  new StudentLogic();
        }

        public decimal CalculateGPA() => _student.CalculateGPA();
    }

    class Employee : Person
    {
        static int runningId = 0;
        public int Id { get; private set; } = runningId++;

        public Employee() : base() { }
        public Employee(string FirstName, string LastName)
            : base(FirstName, LastName) { }
        public Employee(string FirstName, string LastName, DateTime Birthdate)
            : base(FirstName, LastName, Birthdate) { }

        public new string WriteValue() { return $"{Id}: {base.WriteValue()}"; }
    }

    class TeachingAssistant : Employee, IStudent
    {
        private IStudent _student;

        public IList<decimal> Grades
        {
            get
            {
                return _student.Grades;
            }
            set
            {
                _student.Grades = value;
            }
        }

        public int StudentNumber
        {
            get
            {
                return _student.StudentNumber;
            }
        }

        public decimal CalculateGPA() => _student.CalculateGPA();

        public TeachingAssistant(string FirstName, string LastName, DateTime Birthdate) : base(FirstName, LastName, Birthdate)
        {
            this._student = new StudentLogic();
        }
    }

    public static void Main(string[] args)
    {
        var p1 = new Person("John", "Smith", new DateTime(1984, 1, 31));
        var s1 = new Student("Jane", "Smith", new DateTime(1984, 08, 22));
        var t1 = new TeachingAssistant("Jane", "Smith", new DateTime(1984, 08, 22));
    }
}


one thing to note is rather than tightly coupling the implementation of IStudent by instantiating the 'StudentLogic' class within the 'Student' and 'TeachingAssistant' classes, we could pass an implementation of 'StudentLogic' to the classes in their constructors, and thus loosely coupling the implementation of IStudent to the classes that leverage. This loose coupling approach is called Inversion of control which we'll discuss in a future post.