Inheritance II 2020
In this section, we'll create a more complex example using Employee class we made in Encapsulation Services.
We want to model various types of employees in a company. We'll create two new classes (SalesPerson and Manager) leveraging the functionality of the Employee class.
In our example, the Manager class extends Employee by recording the number of stock options, while the SalesPerson class maintains the number of sales made.
Here are the new classes:
// Manager.cs namespace Employees { class Manager : Employee { private int numberOfOptions; public int StockOptions { get { return numberOfOptions; } set { numberOfOptions = value; } } } }
// SalesPerson.cs namespace Employees { class SalesPerson : Employee { private int numberOfSales; public int SalesNumber { get { return numberOfSales; } set { numberOfSales = value; } } } }
Here are the other two files: Employee.Internals.cs and Employee.cs:
// Employee.Internals.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Employees { public partial class Employee { public string Name { get { return empName; } set { empName = value; } } public int ID { get { return empID; } set { empID = value; } } public float Pay { get { return curPay; } set { curPay = value; } } public int Age { get { return empAge; } set { empAge = value; } } public string SocialSecurityNumber { get { return empSSN; } set { empSSN = value; } } public static string Company { get { return companyName; } set { companyName = value; } } public Employee() { } public Employee(string name, int id, int age, float pay, string ssn) { empName = name; empID = id; empAge = age; curPay = pay; empSSN = ssn; } } }
// Employee.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Employees { public partial class Employee { private string empName; private int empID; private float curPay; private int empAge; private string empSSN; private static string companyName; public void GiveBonus(float amount) { curPay += amount; } public void DisplayStats() { Console.WriteLine("Name: {0}", empName); Console.WriteLine("ID: {0}", empID); Console.WriteLine("Age: {0}", empAge); Console.WriteLine("SSN: {0}", empSSN); Console.WriteLine("Pay: {0}", curPay); } static void Main(string[] args) { SalesPerson Hermione = new SalesPerson(); Hermione.Age = 23; Hermione.Name = "Hermione"; Hermione.SalesNumber = 125; Console.ReadLine(); } } }
Let's make class hierarchy diagram.
We have two ways of doing this:
- Project -> Add New Item menu options -> Once we click the Add button, we'll be presented with a blank designer surface -> To add types to a class designer, simply drag each file from the Solution Explorer window onto the surface. When we do so, the IDE responds by automatically including all types on the designer surface.
- Simply click View Class Diagram icon.
Here is the Diagram for Employee class Hierarchy.
The SalesPerson and Manager were created using default constructor. We'll add six-argument constructor to the Manager type.
static void Main(string[] args) { Manager Voldemort = new Manager("Voldemort", 65, 87,160000, "234-24-9873", 100); Console.ReadLine(); }
The constructor for Manager class looks like this:
public Manager(string fullName, int age, int empID, float curPay, string ssn, int numbOfOpts) { numberOfOptions = numbOfOpts; ID = empID; Age = age; Name = fullName; Pay = curPay; SocialSecurityNumber = ssn; }
The first issue with this approach is that we defined the SocialSecurityNumber property in the parent as read-only. So, we cannot assign the incoming string parameter to this field.
SocialSecurityNumber = ssn;
The other issue is that we have indirectly create an inefficient constructor, given the fact that under C#, unless we say otherwise, the default constructor of a base class is called automatically before the logic of the derived constructor is executed. After this point, the current implementation accesses various public properties of the Employee base class to establish its state. So, we have really made seven hits (five inherited properties and two constructor calls) during the creation of a Manager object.
To optimize the creation of a derived class, we'll do well to implement our subclass constructors to explicitly call an appropriate custom base class constructor, rather than the default. In this way, we can reduce the number of calls to inherited initialization members. Let's update the constructor of the Manager type using the base keyword:
public Manager(string fullName, int age, int empID, float curPay, string ssn, int numbOfOpts) : base(fullName, age, empID, curPay, ssn) { numberOfOptions = numbOfOpts; }
The base keyword is hanging off the constructor signature, which always indicates a derived constructor is passing data to the immediate parent constructor. In our case, we're explicitly calling the five-argument constructor defined by Employee and saving ourselves unnecessary calls during creation of the child class.
Here is SalesPerson constructor:
public SalesPerson(string fullName, int age, int empID, float curPay, string ssn, int numOfSales) : base(fullName, age, empID, curPay, ssn) { numberOfSales = numOfSales; }
Be aware that once we add a custom constructor to a class definition, the default constructor is silently removed. So, we need to redefine the default constructor for the SalesPerson and Manager types. For instance:
public SalesPerson() { }
Public items are directly accessible from anywhere, while private items cannot be accessed if not from the class that has defined it.
When a base class defines protected data/members, any descendent can access those items. So, if we want to allow the SalesPerson and Manager child classes to directly access the data defined by Employee, we need to update the original Employee class as follows:
public partial class Employee { protected string empName; protected int empID; protected float curPay; protected int empAge; protected string empSSN; protected static string companyName; ... }
The benefit of making members protected in a base class is that derived types no longer have to access the data indirectly using public methods or properties.
A sealed class cannot be extended by other classes. For example, assume we have added yet another class, PartTimeSalesPerson to our program that extends the existing SalesPerson type.
sealed class PartTimeSalesPerson : SalesPerson { public PartTimeSalesPerson(string fullName, int age, int empID, float curPay, string ssn, int numbOfSales) : base (fullName, age, empID, curPay, ssn, numbOfSales) { ... } }
Because sealed classes cannot be extended, one may wonder if it is possible to reuse the code within a class marked sealed. If we want to build a new class that leverages the functionality of a sealed class, our only option is to forego classical inheritance and use the containment/delegation model.
Here are the files we made so far:
// Employee.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Employees { public partial class Employee { protected string empName; protected int empID; protected float curPay; protected int empAge; protected string empSSN; protected static string companyName; public void GiveBonus(float amount) { curPay += amount; } public void DisplayStats() { Console.WriteLine("Name: {0}", empName); Console.WriteLine("ID: {0}", empID); Console.WriteLine("Age: {0}", empAge); Console.WriteLine("SSN: {0}", empSSN); Console.WriteLine("Pay: {0}", curPay); } static void Main(string[] args) { Manager Voldemort = new Manager("Voldemort", 65, 87,160000, "234-24-9873", 100); Console.ReadLine(); } } }
// Employee.Internals.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Employees { public partial class Employee { public string Name { get { return empName; } set { empName = value; } } public int ID { get { return empID; } set { empID = value; } } public float Pay { get { return curPay; } set { curPay = value; } } public int Age { get { return empAge; } set { empAge = value; } } public string SocialSecurityNumber { get { return empSSN; } set { empSSN = value; } } public static string Company { get { return companyName; } set { companyName = value; } } public Employee() { } public Employee(string name, int id, int age, float pay, string ssn) { empName = name; empID = id; empAge = age; curPay = pay; empSSN = ssn; } } }
// Manager.cs namespace Employees { class Manager : Employee { private int numberOfOptions; public Manager() { } public Manager(string fullName, int age, int empID, float curPay, string ssn, int numbOfOpts) : base(fullName, age, empID, curPay, ssn) { numberOfOptions = numbOfOpts; } public int StockOptions { get { return numberOfOptions; } set { numberOfOptions = value; } } } }
// SalesPerson.cs namespace Employees { class SalesPerson : Employee { private int numberOfSales; public SalesPerson() { } public SalesPerson(string fullName, int age, int empID, float curPay, string ssn, int numOfSales) : base(fullName, age, empID, curPay, ssn) { numberOfSales = numOfSales; } public int SalesNumber { get { return numberOfSales; } set { numberOfSales = value; } } } }
// PartTimeSalesPerson.cs namespace Employees { sealed class PartTimeSalesPerson : SalesPerson { public PartTimeSalesPerson(string fullName, int age, int empID, float curPay, string ssn, int numbOfSales) : base (fullName, age, empID, curPay, ssn, numbOfSales) { } } }
In this section, we'll examine the containment/delegation or aggregation which models has-a relationship.
Let's extend our existing Employee class by adding an employee benefits package:
class BenefitPackage { public double ComputePayDeduction() { return 120.5; } }
It doesn't look reasonable to establish an is-a relationship between the BenefitPackage and the employee types. But it should be clear that some sort of relationship exist between the two. Certainly, it makes sense to establish has-a relationship because each employee has-a BenefitPackage. Here is an update for Employee class definition:
protected BenefitPackage empBenefits = new BenefitPackage();
So, with the added line above, we actually contained another object. However, to expose the functionality of the contained object to the outside world needs delegation. Delegation is simply the act of adding members to the containing class which uses the contained object's functionality.
In our example, we could update the Employee class to expose the contained empBenefits object using a custom property as well as using its functionality internally utilizing a new method GetBenefitCost().
So, here is another update for our Main() method interacting with the internal BenefitPackage type.
static void Main(string[] args) { Manager Voldemort = new Manager("Voldemort", 65, 87,160000, "234-24-9873", 100); double cost = Voldemort.GetBenefitCost(); Console.ReadLine(); }
We can define a type (enum, class, interface, struct, and delegate) within the scope of a class. The inner (or nested) type is considered a member of the outer (or nesting) class. To the runtime it can be manipulated like any other member.
Here is the syntax:
public class OuterClass { public class PublicInnerClass {} private class PrivateInnerClass {} }
Here are some characteristics of nested type:
- Nested type allows us to gain complete control over the access level of the inner type, as it may be declared privately.
- A nested type can access private members of the containing class because it is a member of the containing class.
- There are times when a nested type is only useful as a helper for the outer class, and is not intended for use by the outside.
When a type nests another class type, it can create member variables of the type, just as it would for any point of data. But if we want to use a nested type from outside of the containing type, we must qualify it by the scope of the nesting type. Let's look at the following code:
static void Main(string[] args) { OuterClass.PublicInnerClass innerPub; innerPub = new OuterClass.PublicInnerClass(); OuterClass.PrivateInnerClass innerPriv; innerPriv = new OuterClass.PrivateInnerClass(); }
To apply this concept, we're nesting the BenefitPackage within the Employee class type:
public partial class Employee { class BenefitPackage { public enum BenefitPackageLevel { Standard, Gold, Platinum } public double ComputePayDeduction() { return 120.5; } } ... }
We can go deeper by creating an enumeration named BenefitPackageLevel, which documents the various benefit levels an employee may choose. This means we're binding the connection tight between Employee, BenefitPackage, and BenefitPackageLevel.
Now our hierarchy of types includes inheritance, containment, and nested types as shown in the picture below:
Here are our codes so far:
// Employee.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Employees { public partial class Employee { protected string empName; protected int empID; protected float curPay; protected int empAge; protected string empSSN; protected static string companyName; protected BenefitPackage empBenefits = new BenefitPackage(); public class BenefitPackage { public enum BenefitPackageLevel { Standard, Gold, Platinum } public double ComputePayDeduction() { return 120.5; } } public double GetBenefitCost() { return empBenefits.ComputePayDeduction(); } public BenefitPackage Benefits { get { return empBenefits; } set { empBenefits = value; } } public void GiveBonus(float amount) { curPay += amount; } public void DisplayStats() { Console.WriteLine("Name: {0}", empName); Console.WriteLine("ID: {0}", empID); Console.WriteLine("Age: {0}", empAge); Console.WriteLine("SSN: {0}", empSSN); Console.WriteLine("Pay: {0}", curPay); } static void Main(string[] args) { Employee.BenefitPackage.BenefitPackageLevel myBenefitLevel = Employee.BenefitPackage.BenefitPackageLevel.Platinum; Manager Voldemort = new Manager("Voldemort", 65, 87,160000, "234-24-9873", 100); double cost = Voldemort.GetBenefitCost(); Console.ReadLine(); } } }
// Employee.Internals.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Employees { public partial class Employee { public string Name { get { return empName; } set { empName = value; } } public int ID { get { return empID; } set { empID = value; } } public float Pay { get { return curPay; } set { curPay = value; } } public int Age { get { return empAge; } set { empAge = value; } } public string SocialSecurityNumber { get { return empSSN; } set { empSSN = value; } } public static string Company { get { return companyName; } set { companyName = value; } } public Employee() { } public Employee(string name, int id, int age, float pay, string ssn) { empName = name; empID = id; empAge = age; curPay = pay; empSSN = ssn; } } }
// Manager.cs namespace Employees { class Manager : Employee { private int numberOfOptions; public Manager() { } public Manager(string fullName, int age, int empID, float curPay, string ssn, int numbOfOpts) : base(fullName, age, empID, curPay, ssn) { numberOfOptions = numbOfOpts; } public int StockOptions { get { return numberOfOptions; } set { numberOfOptions = value; } } } }
// SalesPerson.cs namespace Employees { class SalesPerson : Employee { private int numberOfSales; public SalesPerson() { } public SalesPerson(string fullName, int age, int empID, float curPay, string ssn, int numOfSales) : base(fullName, age, empID, curPay, ssn) { numberOfSales = numOfSales; } public int SalesNumber { get { return numberOfSales; } set { numberOfSales = value; } } } }
// PartTimeSalesPerson.cs namespace Employees { sealed class PartTimeSalesPerson : SalesPerson { public PartTimeSalesPerson(string fullName, int age, int empID, float curPay, string ssn, int numbOfSales) : base (fullName, age, empID, curPay, ssn, numbOfSales) { } } }
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization