|
Introduction
This second part of a two-part series explores some more key object-oriented programming (OOP) concepts. Specifically, it will discuss using "virtual" and "override" keywords to control inheritance. Also it will cover interfaces and abstract classes.
Virtual and Override
What are these keywords used for? Part 1 discussed inheritance, how one object can inherit and take on the characteristics of another object (e.g., the Employee inherited the Person's characteristics). Let's look back at the code (with a few added properties to make it more complete).
namespace SampleOOP {
public class Employee : Person {
protected double wage;
public double Wage {
get { return wage; }
set { wage = value; }
}
}
//our custom class
public class Person {
//some member variables
private string firstName;
private string lastName;
private string emailAddress;
private System.DateTime birthDate;
//for the member variables, we use getters and setters
//for the properties
public string FirstName {
get {return firstName;}
set {firstName=value;}
}
public string LastName {
get {return lastName;}
set {lastName=value;}
}
public string Email {
get {return emailAddress;}
set {emailAddress=value;}
}
public System.DateTime BirthDate {
get {return birthDate;}
set {birthDate=value;}
}
//a read-only property
public int Age {
get {return CalculateAge( System.DateTime.Now ); }
}
//a protected method. We will cover more on this later
protected int CalculateAge(System.DateTime ThisDate) {
return (ThisDate-birthDate).Days/365;
}
}
}
Source Listing 1.1 The Employee class inherits from the Person class.
"Virtual" and "override" come into play when we have the same method in our base class as we do in our derived class, and we want our method in the derived class (Employee) to be called. Below will demonstrate how to override the method in the base class with a new implementation in our derived class.
Let's override the CalculateAge() method in our base class. Right now it returns the Age in the number of years, but we want our derived class to return the number of days.
First, add the "virtual" keyword to the method definition in the base class:
protected virtual int CalculateAge(System.DateTime ThisDate) {
return (ThisDate-birthDate).Days/365;
}
Next, create a new method with the same calling signature (name and input parameters), but use the keyword "override":
protected override int CalculateAge(System.DateTime ThisDate) {
return (ThisDate-birthDate).Days;
}
Notice that in the base class variables are defined as "private." This means they are only accessible within that base class. Let's change the "birthDate" variable to "protected," which gives our derived class access to it (see Part 1 at http://www.15seconds.com/issue/020402.htm for a full explanation of each). That's all it takes to override the base class implementation of the method. Remember that you can still call the base class method using "base." For example:
return base.CalculateAge( System.DateTime.Now );
Lastly, I want to mention how to use the "new" keyword to replace a method in the base class. If you cannot declare the base class method as "virtual," use the "new" keyword when declaring the method in the derived class. It is very important to remember that this will essentially hide all of the methods with the same name in the base class, regardless of the signature.
protected new int CalculateAge(System.DateTime ThisDate) {
return (ThisDate-birthDate).Days;
}
Testing the Behavior
To test this demonstration, let's create a bare-bones console application. In your code above, copy and paste the following:
using System;
using SampleOOP;
class App {
public static void Main(string[] args) {
SampleOOP.Employee emp = new SampleOOP.Employee();
emp.Wage=85000;
emp.FirstName="Rob";
emp.LastName="Chartier";
emp.BirthDate = System.DateTime.Parse("December 12, 1974");
Console.WriteLine(emp.FirstName + " " + emp.LastName + " is " + Convert.ToString(emp.Age) + " days old.");
}
}
Make sure your namespace SampleOOP directly follows the code above, in the same file, and compile with the command:
csc testconsole.cs
and run it by using the executable:
testconsole
This shows how to create an instance of our derived Employee class and call its method, which overrides the method in the base class. To make things clear, temporarily remove that method in the Employee class, and then compile and test it. Notice how it is returning the number of years (calling the method in the base class). Also take time to experiment using the "new" keyword to see how it affects the base class.
Interfaces
Interfaces are a fairly easy concept to understand. Consider it a simple contract between the class that is implementing the interface and the interface itself. That is, it states that the implementing class has to contain the specific set of methods (and arguments) that are defined in the interface. When a class has all of the required methods it is said to "implement that interface." Let's create an example to illustrate this.
This very simple interface will resemble a stack. A stack essentially will have methods to push data onto it, and then pop data off of the top of the list. Its behavior is First In Last Out (FILO). Our stack will deal only with strings.
public interface StringStack {
void clear();
void Push(string item);
string Pop();
}
Notice the keyword "interface" that marks this class as being an interface class. Also notice that none of the methods have any implementation details because our class that implements the interface will take care of those details. Let's create a class to do this.
public class MyStack : StringStack {
private System.Collections.ArrayList stack = new System.Collections.ArrayList();
public void clear(){
stack.Clear();
}
public void Push(string item){
stack.Add(item);
}
public string Pop(){
if(stack.Count-1 >= 0) {
string tmp=(string)stack[stack.Count-1];
stack.RemoveAt(stack.Count-1);
return tmp;
}
return "";
}
}
The full source code for this example can be found in the stack.cs ZIP file.
That's all it takes to create and implement any sort of custom interface. Notice this example used a System.Collections.ArrayList to hold the data in the stack. Anyone can rewrite the implementation (for example, using a simple array, or File IO. Just as long as it fully implements the interface, one could easily use either implementation interchangeably.
There are many constructs in the .NET Framework that are created to specifically take advantage of interfaces. Let's consider the IEnumerable and IEnumerator interfaces. If we were to create a class that implemented these two interfaces, our class could enumerate its contents with the "for each" construct in C#. So, imagine giving our Person class the ability to hold many People and the ability to enumerate over them with a mere few lines of code.
Once you dive into Design patterns in more detail, you will see more information regarding where to use interfaces. If you are really interested, read about the Builder pattern" at http://www.vbcity.net/net/article.aspx?cid=2&y=2002&m=2&d=24.
Abstract Classes
Next we will cover abstract classes, and explain the difference between abstract classes and interfaces.
An abstract class is the same as any other class. It declares methods, but leaves some or all of them unimplemented. Note that once you have a method in your class that is declared as abstract, the class itself will also need to be marked as abstract.
Let's refer back to the SampleOOP namespace we created above. If we did not want to implement the CalculateAge() method in our base class, instead, we could force all classes that are derived from it to implement it on their own. We will want to create an abstract method in our base class to force derived classes to implement that method on their own. Let's look at an example of how we would change our class to abstract with the one abstract method.
We need to do two things. The first is to add the "abstract" keyword to the class definition.
public abstract class Person {
Next, we modify our method to also include the "abstract" keyword. We also obviously remove the implementation of the method because the derived classes will implement that method on their own.
protected abstract int CalculateAge(System.DateTime ThisDate);
So our base class is all set up now. Let's move on to our derived class and see what we need to do in order to take advantage of it.
Abstract classes are very similar to interfaces because the derived class must implement all methods that are marked as abstract. That is the contract between the base and derived class. Thus, in our derived class we use the same method as we did previously:
protected override int CalculateAge(System.DateTime ThisDate) {
return (ThisDate-birthDate).Days;
}
Notice the "override" keyword is still a necessity here. Try to remove the "override" keyword and attempt to compile it to see what error the compiler will give you. One thing to remember is that no class can be created where all the members are not implemented. So if you were to attempt to create (and then compile) an abstract class, the compiler would throw an error. Just, derive a new class from that abstract class, and you could implement the abstract methods on your own.
Last, we should cover the difference between an interface and an abstract class. Essentially we use interfaces when we do not want to implement ANY method in our base class, and allow our derived classes to do all of the implementation. In abstract classes we see our base class having one or more methods that are partially or fully implemented. Interfaces are very useful when you want to force derived classes to a very specific and known contract.
When you create an abstract class and all methods are not implemented, it is essentially the same as an interface. The exception is that if you do it the one way, you can't inherit from more than just the one class, but you could easily gain by doing it with an interface. Like Java, C#, for example, only supports a single line of inheritance and interfaces make up for the lack of multiple inheritance.
Conclusion
This covers most of the OOP development materials you'll need to to start building your own objects. Test each feature, and avoid overcomplicating things. There are a lot of people in the industry that will swear by a 100 percent OO approach. It has advantages, such asencapsulation and modularization, but it also has a few disadvantages, such as speed and performance. It is up to us to really understand and know when to draw the line between creating a really reusable object hierarchy and killing the performance of our application.
About the Author
Robert Chartier has developed IT solutions for more than nine years with a diverse background in both software and hardware development. He is internationally recognized as an innovative thinker and leading IT architect with frequent speaking engagements showcasing his expertise. He's been an integral part of many open forums on cutting-edge technology, including the .NET Framework and Web Services. His current position as vice president of technology for Santra Technology (http://www.santra.com) has allowed him to focus on innovation within the Web Services market space.
He uses expertise with many Microsoft technologies, including .NET, and a strong background in Oracle, BEA Systems, Inc.'s BEA WebLogic, IBM, Java 2 Platform Enterprise Edition (J2EE), and similar technologies to support his award-winning writing. He frequently publishes to many of the leading developer and industry support Web sites and publications. He has a bachelor's degree in Computer Information Systems.
Robert Chartier can be reached at rob@aspfree.com.
|