Polymorphism 2020
Polymorphism is one of the pillars of OOP.
This captures a language's ability to treat related objects in a similar manner. Polymorphism allows a base class to define a set of members (polymorphic interface) that are available to all descendents. A class's polymorphic interface is constructed using virtual or abstract members.
The virtual member is a member in a base class that defines a default implementation that may be overridden by a derived class. In contrast, an abstract member is a member in a base class that does not provide a default implementation, but does provide a signature for interface. When a class derives from a base class defining an abstract method, it must be overridden by a derived type.
In either case, when derived types override the members defined by a base class, they are essentially redefining how they respond to the same request.
Let's put some polymorphic features to our Employee class in the previous Chapter Inheritance II.
The Employee defined a method GiveBonus():
public partial class Employee { public void GiveBonus(float amount) { curPay += amount; } ... }
Since this method has been defined with the public keyword, we cannot give bonuses to salespeople and managers as well as part-time salespeople:
static void Main(string[] args) { Manager voldemort = new Manager("Voldemort", 65, 87,160000, "234-24-9873", 100); voldemort.GiveBonus(200); voldemort.DisplayStats(); Console.WriteLine(); SalesPerson hermione = new SalesPerson("Hermione", 25, 37, 90000, "289-43-7113", 300); hermione.GiveBonus(105); hermione.DisplayStats(); Console.ReadLine(); }
If we run it, we get the following output:
Name: Voldemort ID: 65 Age: 87 SSN: 234-24-9873 Pay: 160200 Name: Hermione ID: 25 Age: 37 SSN: 289-43-7113 Pay: 90105
The current design has a problem which is that the publicly inherited GiveBonus() method operates identically for all subclasses. Ideally, the bonus of a salesperson or part-time salesperson should take into account the number of sales. So, how can related types respond differently to the same request?
Polymorphism provides a way for a child class to define its own version of a method defined by its base class using method overriding. If a base class wants to define a method that may be overridden by a child class, it must mark the method with virtual keyword:
public partial class Employee { public virtual void GiveBonus(float amount) { curPay += amount; } ... }
Then, a subclass which wants to change the implementation details of a virtual method, it should use override keyword. So, the SalesPerson and Manager override GiveBonus() as follows:
// Manager.cs class Manager : Employee { ... public virtual void GiveBonus(float amount) { base.GiveBonus(amount); System.Random r = new System.Random(); numberOfOptions += r.Next(500); } } }
// SalesPerson.cs class Manager : Employee { ... public override void GiveBonus(float amount) { base.GiveBonus(amount); System.Random r = new System.Random(); numberOfOptions += r.Next(500); } }
Note how each overridden method is leveraging the default behavior using the base keyword. In this way, we don't have to completely re-implement the logic behind GiveBonus(), but reuse the default behavior of the parent class.
We may also override DisplayStats() method for the two child classes. For example for the Manager looks like this:
public override void DisplayStats() { base.DisplayStats(); Console.WriteLine("Number of Stock Options: {0}", numberOfOptions); }
OK!
Let's take a look at our codes again.
Here are the files 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 virtual void GiveBonus(float amount) { curPay += amount; } public virtual 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); voldemort.GiveBonus(200); voldemort.DisplayStats(); Console.WriteLine(); SalesPerson hermione = new SalesPerson("Hermione", 25, 37, 90000, "289-43-7113", 300); hermione.GiveBonus(105); hermione.DisplayStats(); Console.ReadLine(); } } }
// Employee.Internals.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Employees { 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 using System; 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; } } public override void GiveBonus(float amount) { base.GiveBonus(amount); System.Random r = new System.Random(); numberOfOptions += r.Next(500); } public override void DisplayStats() { base.DisplayStats(); Console.WriteLine("Number of Stock Options: {0}", numberOfOptions); } } }
// 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; } } public override void GiveBonus(float amount) { int salesBonus = 0; if (numberOfSales > 100) { salesBonus = 10; } else salesBonus = 3; base.GiveBonus(amount *salesBonus); } public override void DisplayStats() { base.DisplayStats(); System.Console.WriteLine("Number of Sales: {0}", numberOfSales); } } }
// 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) { } } }
And here is the output we get:
Name: Voldemort ID: 65 Age: 87 SSN: 234-24-9873 Pay: 160200 Number of Stock Options: 145 Name: Hermione ID: 25 Age: 37 SSN: 289-43-7113 Pay: 91050 Number of Sales: 300
The seal keyword can be applied to a class type to prevent other types from extending its behavior via inheritance. Sometimes we may not want to seal an entire class, but simply want to prevent derived types from overriding particular virtual methods. For example, assume we don't want part-time salesperson to obtain customized bonuses. To prevent the PartTimeSalesPerson class from overriding the virtual GiveBonus() method, we could seal this method in the SalesPerson class as follows:
class SalesPerson : Employee { ... public override sealed void GiveBonus(float amount) { ... } }
The SalesPerson class has overridden the virtual GiveBonus() method defined in the Employee class. But it has explicitly marked it as sealed. So, if we attempt to override this method in the PartTimeSalesPerson class, we'll receive compile-time errors.
The Employee base class has been designed to supply protected member variables for its descendents as well as supply two virtual methods (GiveBonus() and DisplayStats()) that may be overridden by a descendent. However, there is a rather odd byproduct of the current design: we can create instances of the Employee base class:
Employee e = new Employee();
So far in our example, the only purpose the Employee base class is to define common members for all subclasses. We don't want anybody to create a direct instance of this base class. It makes sense since the Employee class is too general and nebulous in nature. So, we want to prevent the ability to directly create a new Employee object by using abstract keyword:
abstract partial class Employee { ... }
Then, the following code will give a compile-time error:
Employee e = new Employee();
Here is our class hierarchy diagram:
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization