What you will learn in this chapter:
wrox.com code downloads for this chapter
You can find the Wrox.com code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=9781118336922 on the Download Code tab. The code in the Chapter16 folder is individually named according to the names throughout the chapter.
This chapter expands the concept of inheritance. Although you have been using inheritance since the first chapter, not much has been said about it. The reason for the delay is because inheritance doesn't make much sense until you understand and appreciate OOP. My guess is that you do see what OOP brings to the table and are ready to dig into the topic of inheritance. In this chapter you learn the details of inheritance necessary to use it properly in your own programs.
As you read this chapter, keep in mind that inheritance was added to OOP languages to make the programmer's life easier. Although the end user may not see any advantages to inheritance and polymorphism, you can have a greater appreciation of how programmers can make program code simpler.
Until now, inheritance has been a concept sitting in the background, making your life easier every time you designed a user interface for your programs. Indeed, you have probably glossed over that the program line
public class frmMain : Form
that appears in every program you've written has enabled you to inherit all the basic functionality of a Windows form without needing to write any of that code. The colon in the preceding statement could be verbalized as “inherits from.”
The concept of inheritance is built upon the notion that many objects share similar properties. In the preceding statement, you state that you want to create a new class named frmMain that inherits all the functionality of a basic Windows form. Inheriting this functionality means that you don't need to write, test, debug, and maintain that inherited code. Therefore, the driving force behind inheritance is to simplify writing code. Inheritance makes it possible for your code to extend one class to suit your specific needs. Simply stated, inheritance is the ability to take one class and extend that class to suit a similar, albeit different, purpose. An example can help explain inheritance.
Some time ago I was contracted to write a program for a real estate investor. The type of real estate the investor purchased could be classified as apartments, commercial properties (such as small strip malls), and residential homes. I sat down with the investor and asked her to describe the type of information that she needed to track with her investments. Table 16.1 is taken from my notes as she described her needs.
Apartments | Commercial | Residential |
Purchase price | Purchase date | Address |
Purchase date | Address | Purchase price |
Address | Rent per month | Purchase date |
Monthly mortgage payment | Purchase price | Square feet |
Insurance | Property taxes | Number of bedrooms |
Property taxes | Insurance | Number of bathrooms |
Covered parking | Mortgage payment | Basement? |
Storage units | Parking spaces | Fireplace(s) |
Number of bedrooms | Restroom facilities | Garage size (cars) |
Number of bathrooms | Handicap parking | Rent per month |
Rent per month | Lot size |
Given the data requirements presented in Table 16.1, I could design the three classes with properties that would track the information she required. Looking at the information she needed, you can simplify each property type by removing those pieces of information common to all investment types. Table 16.2 shows these common properties.
Information Common to All Properties |
Purchase price |
Purchase date |
Address |
Property taxes |
Insurance |
Mortgage payment |
Rent per month |
If you remove these common pieces of information from each investment type, you can simplify Table 16.1 to what is shown in Table 16.3. (This process to factor out duplicated data is similar to the normalization process discussed for databases.)
Apartments | Commercial | Residential |
Covered parking | Parking spaces | Square feet |
Storage units | Restroom facilities | Number of bedrooms |
Number of bedrooms | Handicap parking | Number of bathrooms |
Number of bathrooms | Basement? | |
Fireplace(s) | ||
Garage size (cars) | ||
Lot size |
Less information is contained in Table 16.3 than in Table 16.1 because of the common information all three property types share in Table 16.2 is removed. You can express the relationships for the data as shown in the UML class diagrams in Figure 16.1. (You will learn about the removeSnow() method later in the section titled “The virtual Keyword.”)
As you can see in Figure 16.1, I used the UML notation to give a first approximation of how to organize the data for the investment types. At the top of the figure is clsBuilding. clsBuilding, which contains all the properties common to all the investment types and is called the base class. You can think of the base class as a common denominator of data for all the classes. (This is not a new concept; you've been inheriting all of a Windows form's properties using the :Form expression in frmMain in all your programs.)
Any classes that want to use the properties and methods of the base class are called a derived class. For Figure 16.1, clsBuilding is the base class, and classes clsApartment, clsCommercial, and clsHome are derived classes. You also hear the base class referred to as the parent class and a derived class as a child class. The interpretations are the same.
Referring to Figure 16.1 you can notice that three arrows point from the derived classes to the base class. Each of these arrows can be verbalized as “is a.” That is, clsApartment “is a” clsBuilding. Using straight English, you can say, “An apartment object is a type of building object.” Likewise, a commercial object is a type of building object. The arrows in the UML diagram indicate the nature of the inherited relationships that exist between classes.
Referring to Figure 16.1, you can see the seven common properties all investment types share in clsBuilding with the data type for each property indicated. However, unlike the public (+) and private (-) access specifiers you learned in Chapter 9, a new access specifier (#) is used. The sharp sign (#) denotes the protected access specifier. The protected access specifier is needed because of the symbiotic relationship ( “is a”) formed between the base class and the derived classes.
To understand why the protected class is used, consider how things would work if you were limited to either a public or a private access specifier. First, consider the private access specifier. If you use the private access specifier in clsBuilding, each method and property in clsBuilding has class scope. As you already know, this means that those properties and methods would not be visible outside the class. Stated another way, any private property or method in clsBuilding would not be available to any outside class, including the three derived classes. Therefore, nothing in the program would have access to the base properties and methods. If you can't access them, they are virtually worthless to the derived classes.
Now consider the other alternative: making all the properties and methods public. If you did that, you just threw away all the benefits that encapsulation brings to the party. Not only can the derived classes gain access to the properties and methods of the base class, but so can every other object in the program. As you learned in earlier chapters, encapsulation enables you to protect your data from evil agents who want to wreak havoc on your program.
Using either the public or private access specifier poses a dilemma: two choices, both bad. The protected access specifier solves this problem. The protected access specifier enables all derived classes to have access to those properties and methods defined with the protected access specifier in the base class. By your using the sharp sign in Figure 16.1 before the properties defined in clsBuilding, all three derived classes have access to those properties. However, any object in the program that is not derived from the base class does not have direct access to those protected properties. That is, protected properties and methods appear as private properties and methods to all but the derived class(es). The protected access specifier similar to a high school clique in that the base and derived classes can share information that people outside the clique don't know about. This enables the base and derived classes to encapsulate the data that they need without exposing it outside those symbiotic classes.
Just to drive the point home, given a line in clsBuilding,
protected decimal purchasePrice;
you could have the line,
purchasePrice = 150000M;
in clsHome and it would be perfectly acceptable. The reason is that the protected keyword for the definition of purchasePrice in clsBuilding is completely in scope within clsHome. Therefore, protected data definitions in the base class are within scope for any of its derived classes.
You might be asking what inheritance gets you. Well, to begin with, if you didn't have the inherited relationships shown in Figure 16.1, each of the derived classes would need its own copy of the data shown in Table 16.2. That would also mean that each of those properties would need its own get and set property methods. The same would be true for any methods that might be shared in the derived classes, such as RemoveSnow(), shown in Figure 16.1.
Inheritance enables you to write less code by sharing properties and methods between the base and derived classes. Writing less (duplicate) code means less testing, debugging, and maintenance. It also follows that if you need to change a protected property or method, you must change it in only one place, and all the derived classes can immediately take advantage of it.
The next step is to put everything that we've discussed thus far into a program. The next Try It Out provides the code to implement our investment property problem.
Listing 16-1: Source Code for clsBuilding (clsBuilding.cs)
using System; public class clsBuilding { //--------------------- Symbolic constants ----------------- public const int APARTMENT = 1; public const int COMMERCIAL = 2; public const int HOME = 3; //--------------------- Instance variables ----------------- protected string address; protected decimal purchasePrice; protected decimal monthlyPayment; protected decimal taxes; protected decimal insurance; protected DateTime datePurchased; protected int buildingType; string[] whichType = {"", "Apartment", "Commercial", "Home" }; //--------------------- Constructor ------------------------ public clsBuilding() { address = "Not closed yet"; } public clsBuilding(string addr, decimal price, decimal payment, decimal tax, decimal insur, DateTime date, int type):this() { if (addr.Equals("") == false) address = addr; purchasePrice = price; monthlyPayment = payment; taxes = tax; insurance = insur; datePurchased = date; buildingType = type; } //--------------------- Property Methods ------------------- public string Address { get { return address; } set { if (value.Length != 0) address = value; } } public decimal PurchasePrice { get { return purchasePrice; } set { if (value > 0M) purchasePrice = value; } } public decimal MonthlyPayment { get { return monthlyPayment; } set { if (value > 0M) monthlyPayment = value; } } public decimal Taxes { get { return taxes; } set { if (value > 0M) taxes = value; } } public decimal Insurance { get { return insurance; } set { if (value > 0M) insurance = value; } } public DateTime DatePurchased { get { return datePurchased; } set { if (value.Year > 2008) datePurchased = value; } } public int BuildingType { get { return buildingType; } set { if (value >= APARTMENT && value <= HOME) buildingType = value; } } //--------------------- General Methods -------------------- /***** * Purpose: Provide a basic description of the property * * Parameter list: * string[] desc a string array to hold description * * Return value: * void * * CAUTION: Method assumes that there are 3 elements in array ******/ public void PropertySummary(string[] desc) { desc[0] = "Property type: " + whichType[buildingType] + ", " + address + ", Cost: " + purchasePrice.ToString("C") + ", Monthly payment: " + monthlyPayment.ToString("C"); desc[1] = " Insurance: " + insurance.ToString("C") + " Taxes: " + taxes.ToString("C") + " Date purchased: "+ datePurchased.ToShortDateString(); desc[2] = " "; } }
public enum bldgType {APARTMENT = 1, COMMERCIAL, HOME}
set { if (value >= (int) bldgType.APARTMENT && value <= (int) bldgType.HOME) { buildingType = value; } }
public clsBuilding() { address = "Not closed yet"; }
public clsBuilding(string addr, decimal price, decimal payment, decimal tax, decimal insur, DateTime date, int type):this() { if (addr.Equals("") == false) { address = addr; } purchasePrice = price; monthlyPayment = payment; taxes = tax; insurance = insur; datePurchased = date; buildingType = type; }
Listing 16-2: The frmMain Code for the Application
using System; using System.Windows.Forms; public class frmMain : Form { DateTime myTime; // instance members clsBuilding myBldg; clsApartment myApt; clsCommercial myComm; clsHome myHome; private ListBox lstMessages; private Button btnShow; private Button btnRemoveSnow; private Button btnClose; #region Windows code public frmMain() { InitializeComponent(); // Initialize the form myTime = DateTime.Now; myBldg = new clsBuilding(); // A base object // Derived objects myApt = new clsApartment("123 Jane Dr., Cincinnati, OH 45245", 550000, 6000, 15000, 3400, myTime, 1); myComm = new clsCommercial("4442 Parker Place, York, SC 29745",1200000, 9000, 22000, 8000, myTime, 2); myHome = new clsHome("657 Dallas St, Ringgold, GA 30736", 260000, 1100, 1750, 900, myTime, 3); } public static void Main() { frmMain main = new frmMain(); Application.Run(main); } // Show each of the properties… private void btnShow_Click(object sender, EventArgs e) { string[] desc = new string[3]; myApt.PropertySummary(desc); ShowProperty(desc); myComm.PropertySummary(desc); ShowProperty(desc); myHome.PropertySummary(desc); ShowProperty(desc); } private void ShowProperty(string[] str) { int i; for (i = 0; i < str.Length; i++) { lstMessages.Items.Add(str[i]); } } private void btnClose_Click(object sender, EventArgs e) { Close(); } }
myApt.PropertySummary(desc); ShowProperty(desc);
Listing 16-3: Source for clsApartment (clsApartment.cs)
using System; class clsApartment : clsBuilding { //--------------------- Instance variables ----------------- private int units; private decimal rentPerUnit; private double occupancyRate; //--------------------- Constructor ------------------------ public clsApartment():base() { } public clsApartment(string addr, decimal price, decimal payment, decimal tax, decimal insur, DateTime date, int type) : base(addr, price, payment, tax, insur, date, type) { buildingType = type; // Apartment type from base } //--------------------- Property Methods ------------------- public int Units { get { return units; } set { if (value > 0) units = value; } } public decimal RentPerUnit { get { return rentPerUnit; } set { if (value > 0M) rentPerUnit = value; } } public double OccupancyRate { get { return occupancyRate; } set { if (value > 0.0) occupancyRate = value; } } //--------------------- General Methods -------------------- public override string RemoveSnow() { return "Called John's Snow Removal: 859.444.7654"; } } ================================================================ using System; class clsCommercial : clsBuilding { //--------------------- Instance variables ----------------- private int squareFeet; private int parkingSpaces; private decimal rentPerSquareFoot; //--------------------- Constructor ------------------------ public clsCommercial(string addr, decimal price, decimal payment, decimal tax, decimal insur, DateTime date, int type) : base(addr, price, payment, tax, insur, date, type) { buildingType = type; // Commercial type from base } //--------------------- Property Methods ------------------- public int SquareFeet { get { return squareFeet; } set { if (value > 0) squareFeet = value; } } public int ParkingSpaces { get { return parkingSpaces; } set { parkingSpaces = value; } } public decimal RentPerSquareFoot { get { return rentPerSquareFoot; } set { if (value > 0M) rentPerSquareFoot = value; } } //--------------------- General Methods -------------------- public override string RemoveSnow() { return "Called Acme Snow Plowing: 803.234.5566"; } } =============================================================== using System; class clsHome : clsBuilding { //--------------------- Instance variables ----------------- private int squareFeet; private int bedrooms; private double bathrooms; private decimal rentPerMonth; //--------------------- Constructor ------------------------ public clsHome(string addr, decimal price, decimal payment, decimal tax, decimal insur, DateTime date, int type) : base(addr, price, payment, tax, insur, date, type) { buildingType = 3; // Home type from base } //--------------------- Property Methods ------------------- public int SquareFeet { get { return squareFeet; } set { if (value > 0) squareFeet = value; } } public int BedRooms { get { return bedrooms; } set { bedrooms = value; } } public double BathRooms { get { return bathrooms; } set { bathrooms = value; } } public decimal RentPerMonth { get { return rentPerMonth; } set { if (value > 0M) rentPerMonth = value; } } //--------------------- General Methods -------------------- }
class clsApartment : clsBuilding
public clsApartment(string addr, decimal price, decimal payment, decimal tax, decimal insur, DateTime date, int type) : base(addr, price, payment, tax, insur, date, type)
The capability for a derived class to use a method in the base class is a one-way relationship. That is, the derived class can call a base class method, but the base class cannot directly call a derived class method. For example, suppose clsCommercial contains a public method named HouseCleaning(). If myBldg is a clsBuilding object, you cannot use
myBldg.HouseCleaning(); // Error!!
The compiler throws an error and informs you that it cannot find a definition for HouseCleaning(). This conclusion is true even if you use the public access specifier for the HouseCleaning() method. The reason you can't perform a base-to-derived method call is that the “is a” relationship shown in Figure 16.1 is a one-way street. That is, a clsCommercial object assumes all the property and methods of a clsBuilding object, but a clsBuilding object does not assume all the properties and methods of a clsCommercial object.
As I mentioned earlier, the base class is typically used to serve as a repository for all the properties shared among the derived classes. However, it is possible that the base class is so nondescript that instantiating an object of the base class doesn't make sense. For example, you might create a tree class called clsDeciduous that has the properties leafColor, barkStyle, matureHeight, and ringCount. The derived classes might be clsOak, clsMaple, clsWillow and so on. The problem is that even though all the derived classes have leaves, bark, a mature height, and a ring count, there are so many trees with these characteristics that it makes no sense to instantiate a clsDeciduous object. Only the details found in the derived classes, with the base class, have enough information to make an object useful.
To prevent instantiation of the base class, use the abstract keyword:
public abstract class clsDecidious { // The class code… }
If a class is defined by means of the abstract keyword, as shown here, you cannot instantiate an object of that class. This means that a statement like the following draws a compiler error telling you that you cannot instantiate an object of an abstract class:
clsDecidious myLeafyTree = new clsDecidious();
If you can't instantiate an object of the class, why use it?
By defining a class using the abstract keyword, you are telling the users of the class two things. First, they cannot instantiate an object of this class, which tips them off to the second reason. Second, and more important, the user must define derived classes to capture the functionality embodied in the base class. Indeed, there is no reason to use the base class in the absence of derived classes.
If you define a method using the abstract keyword, the derived classes must implement the method. There is no code in the base class for the method. In this way, abstract classes and methods are similar to interfaces in that interfaces contain no code either. However, interfaces cannot contain constructors.
Chapter 1 mentions that polymorphism is one of the three pillars of object-oriented programming. At that time I dismissed the topic, saying that the word polymorphism is derived from the Greek meaning “many shapes” and that was all that was said. Now that you understand what inheritance is, you are ready to more completely appreciate what polymorphism is.
Instead of sticking with the concept of “many shapes,” perhaps the definition should be amended to mean “many messages.” In essence, polymorphism means that you can send the same message to a group of different classes and that each class will know how to respond correctly to that message.
public virtual string RemoveSnow() { return whichType[buildingType] + ": No snow removal service available."; }
public override string RemoveSnow() // clsApartment { return "Apartment: Call John's Snow Removal: 859.444.7654"; } public override string RemoveSnow() // clsCommerical { return "Commercial: Call Acme Snow Plowing: 803.234.5566"; }
public class clsRecreational : clsHome { // Class code }
private void btnRemoveSnow_Click(object sender, EventArgs e) { lstMessages.Items.Add(myApt.RemoveSnow()); lstMessages.Items.Add(myComm.RemoveSnow()); lstMessages.Items.Add(myHome.RemoveSnow()); lstMessages.Items.Add(""); }
Home: No snow removal service available.
Inheritance gives you a way to avoid duplicate code that could exist without the parent-child relationship. Of course, using inheritance requires that you have access to the source code for the class. Extension methods, however, allow you to extend the functionality of a class without the need for the source code.
You can think of extension methods as static methods that you can graft onto an existing class without having the source code for that class. The easiest way to understand what extension methods bring to the party is by example-like that in the next Try It Out.
Suppose you have a program that needs to verify that the user entered a valid Social Security number (SSN). A valid SSN is of the format ddd-dd-dddd, which means a field of three digits, a dash, two digits, another dash, and finally a four-digit sequence. What you want to do is extend the String class to add a method that checks for a valid SSN. Figure 16.4 shows a sample run of the program you are about to write. The next section presents an example of how you can use extension methods.
Listing 16-4: Source Code for clsExtension. (clsExtension.cs)
using System; using System.Text.RegularExpressions; namespace StringExtensionMethods { public static class clsExtension { // For details on regualr expression pattern options, see: // http://www.regular-expressions.info/reference.html public static bool CheckValidSSN(this string str) { int len = str.Length; Regex pattern = null; // Is it xxx-xx-xxxx or xxxxxxxxx? if (len == 11 || len == 9) { if (len == 9) { // Accept 9 digit characters pattern = new Regex(@"d{9}"); } else { // Accept ddd-dd-dddd pattern = new Regex(@"d{3}-d{2}-d{4}"); } return pattern.IsMatch(str); } else { return false; // Not valid } } } }
d{3}-d{2}-d{4}
Listing 16-5: Program to Use Extension Methods. (frmMain.cs)
using System; using System.Windows.Forms; using StringExtensionMethods; public class frmMain : Form { private Label label1; private Label lblResult; private TextBox txtSSN; private Button btnVerify; private Button btnClose; #region Windows Code public frmMain() { InitializeComponent(); } [STAThread] public static void Main() { frmMain main = new frmMain(); Application.Run(main); } private void btnVerify_Click(object sender, EventArgs e) { string str = txtSSN.Text; if (str.CheckValidSSN()) lblResult.Text = "Valid"; else lblResult.Text = "Invalid"; } private void btnClose_Click(object sender, EventArgs e) { Close(); } }
using StringExtensionMethods;
if (str.CheckValidSSN()) lblResult.Text = "Valid"; else lblResult.Text = "Invalid";
This chapter completed your study of the three pillars of object-oriented programming: encapsulation, inheritance, and polymorphism. Throughout the importance of encapsulating your data inside a class or methods has been stressed. In this chapter you learned how the protected access specifier permits you to encapsulate data, yet share it with derived classes as needed. You learned how the base class serves as a common denominator for related, yet distinct, classes. You also learned how polymorphism enables you to have each derived class react to messages in a way that is appropriate for it. Finally, you saw how extension methods can extend the functionality of a class by “tacking on” new methods without having to use inheritance.
You can find the answers to the following exercises in Appendix A.
public clsJunior(string addr, int status, decimal minimum) : base(addr, status, minimum)
Topic | Key Points |
Inheritance | The ability to extend existing classes to accommodate the need fordifferent properties and methods without duplicating code. |
Why inheritance is useful | It enables you to reuse and simplify your code. |
Base (parent) class | The class from which other classes are derived. |
Derived (child) class | The class(es) that inherit the properties and methods from the base class. |
protected | An access specifier that enables properties and methods to be shared between base and derived classes. |
Polymorphism | The capability for each derived class to respond in its own way to program events. |
Extension methods | How to extend class functionality without using inheritance. |
18.226.180.133