OOP Quick Start for Developers
Objective
Demonstrate object-oriented programming techniques in practical terms. This means that the code does not start out perfect. It evolves to accommodate changes and therefore better demonstrates the power of object oriented programming. It is also much more instructive to see how these changes are made, because it teaches how to make the changes. The finished product is a by product of work effort, and programmers need to see the interim mistakes in order to know how to handle them.
Summary
This is a simple walk through to describe some OOP techniques and where to use them. Many developers know what OOP is but they haven't been mentored to know where to apply those techniques. This is an example that helps bridge that gap. You’ll see where access modifiers are used and how they often get changed while developing code. There are examples of choosing which class to place functionality in. You’ll also see how an inheritance hierarchy evolves while it is being created. The usage of interfaces and abstract classes is also discussed.
Tutorial
For this project, we will process groups of people in various ways. We’ll start with a person, and build out functionality to demonstrate why we use inheritance, interfaces, composition, and information hiding.
If you want to follow along, go ahead and start with a new Visual C# Project. I called mine "OOP."
First, we need a basic person class to work with. Let’s just start with the information that the person class deals with. Later we will add the actions that can be performed for a given person.
This person has the basic pieces of necessary information. We want to handle the birth day in a special way by ignoring the time on that day. We do this so that we can make date comparisons for that given day much simpler. We use a private modifier to indicate that _birthdate can only be used within the class. When accessing the class to retrieve or modify the birth day we can use the publicly accessible BirthDate.
There are 4 levels of accessibility modifiers.
- Public – Accessible to all
- Private – Accessible only to the members of the class in which it is defined
- Protected – Just like private, except it is accessible to all derived child classes too
- Internal – Just like public, except it is only publicly accessible to members within the defining assembly
Accessibility modifiers can be applied to classes, methods, properties, and variables. There are defaults (for example all class members are private by default), but my preferences is to always state it explicitly to avoid confusion.
Now that we have a person, let’s move on to grouping people together. We’ll need a people class to hold several persons.
The easiest way to create a group of people is to make use of existing functionality and compose it into our class. Somebody at Microsoft already built a class that deals with Lists. A list allows you to keep track of many similar items. Always try to make use of libraries provided from the vendor if possible. It has a high chance of good quality because a lot of effort goes into trying to ensure an excellent product reaches market. That works for us, so we’ll re-use it here. We’ll also compose in the Person class that we created earlier.
This introduces several new concepts.
Constructors
The constructor is a special kind of method that is executed whenever an instance of the class is created. It always has the same name as the class and it never returns any results. It is useful for initializing variables such as what we have done above.
Methods
Methods perform actions. The add method above takes a person and adds it to the list.
Generic Types
This list operates on Person and it can do this because it uses Person as its template type. You can have lists of anything you want and they are much more flexible than arrays. You can easily add, remove, and perform search operations on them with their built-in methods.
As a general rule... don't create your own lists, hash tables, or stacks. Use the generic collections instead. Examples include: list, dictionary, queue, and stack.
They are well optimized and of good quality. Your time can be better spent on solving problems.
Using clauses and Namespaces
The using clause allows us to import classes from other namespaces. In particular we used the List class found in System.Collections.Generic. Namespaces provide a way to organize many classes that perform related behaviors. They also prevent naming collisions, so that developers don’t have to worry much about whether someone else used the same name for a member as you did.
Next Step
The next step in this project is to start dealing with people. We need to deal with different types of groups of people in different ways. This means we will have several “is a” relationships for different groups of people. For example, a Tax Household “is a” group of people with one way of doing things. It is different from a Regular Household. A regular household “is a” group of people too, but it has another way of doing things. Let’s define them now so we can continue.
Tax Household
The taxpayer(s) and any individuals who are claimed as dependents on one federal income tax return. A tax household may include a spouse and/or dependents.
Regular Household
All of the individuals living together in the same house most of the time.
Great, so now let’s define a household. It will start simple and then it will evolve as we learn more. This kind of evolution is what makes object oriented programming helpful.
Inheritance
Inheritance allows a derived class to make use of the resources available to itself and also from the parent class. HouseHold derives from People so it gets to use all the stuff available from People. This means Household can work with each of the persons and all of their related information.
Encapsulation and Information Hiding
We want HouseHold to deal with its own housekeeping. Therefore, we are hiding the details of adding a person to the group of people by providing another Add() method here. A house hold will always need a main contact person called a “Head of Household”. This add method also takes that into consideration.
Note that TaxHousehold and HouseHold both deal with people differently.
TaxHouseHold links new dependents to the head of the house hold.
A regular house hold does not care about any such distinction.
base
The “base” term explicitly allows us to directly call a parent’s method.
override
Override indicates that we are redefining a method that was previously provided by a parent class. Override can be used on virtual methods. A virtual method is a method that is open to being redefined.
Next Step
We need to go back to our People class and make the Add() method virtual to allow us to redefine it.
From our earlier household definitions, we already know that there are a few different kinds of Persons that we need to accommodate. They are a regular person, a dependent, and a head of house hold.
Composition
A HeadOfHousehold "has a" list of depedents. If it is a "has a" relationship then you should use composition. If the behavior works from an "is a" relationship then you would best use inheritance. HeadOfHouseHold "is a" Person.
Chaining constructors
The usage of base() above allows a constructor to first call the parent constructor before continuing on with it’s own initialization.
Let’s take a step back and look at what we have now: If you create a class diagram in your project you can see something like this:
It is becoming clear that we never really want to use the People class directly. People will always be used indirectly. Indeed we will use Household, or TaxHousehold or some other derived class that inherits from people. This means that People is “Abstract” so let’s make that explicit.
Polymorphism
Polymorphism is used here to handle type compatibility between parent and child classes. Derived child classes can be passed into anywhere a parent class is used. For example you can add a Dependant to a list of person. You could use a TaxHouseHold in a spot where People is expected. This type compatibility is one way of using polymorphism. The behavior of a derived class is changed by using virtual methods, Polymorphism is not virtual methods itself.
Abstract
An abstract class can define members, but it cannot be used/instanced directly. In this way it defines some functionality and a way of doing things… but it should not be used directly.
You can also use abstract members within a class to force an inherited class to implement missing functionality. This can ensure that others will use the work that you have previously created according to the proper convention.
Our TaxHouseHold class made use of a method called Find() which has not been seen here yet. It is a method that was inherited from People. We hide the implementation details of Find away in the parent (or base) class to make the code easier to maintain in the specialized classes.
Next Step
Ok, so that fairly well fills out the general core of what is needed to expand the functionality. We have defined different kinds of groups of people, and we have defined different kinds of people. The next big requirements pertain to two items:
- View the people in a group
- Perform some processing on the group of people.
To view the people in a group, we can add some Show() methods to the right places. For the processing part, we can calculate the age of dependents and show that instead of their birth date.
Let’s do the view part first, because it is useful and easy. Since everything here tracks back to Person we will start there.
Testing is important, so let’s do that next. A simple command line console application is a good way to prove that your code works as you expect it to. Don't hesitate to create dummy applications to make sure you have coded a solution correctly.
Note: Consider making a unit test for whatever you are working on at any given time. They are easy to write and provide a quick way for you to validate your work.
Great, now we can see what a person has. Let’s do the group of people next.
Since the different households are derived from People, they will get access to the Show() method through inheritance.
There are several different ways to initialize data. Two of them are described above, but I find that they are too verbose most of the time. We should define our own constructor to alleviate the issue. Another good reason for a custom constructor is that we can better enforce that all of the necessary data is provided for an object to perform correctly.
Let’s add some constructors to allow us to write more concise code.
Now we can make Main much shorter.
Showing all of the people like this might be fine for most groups of people, but we have a special case for the TaxHouseHold. The people in that group have dependent relationships which should be shown in a hierarchy. In order to show the hierarchy we need to redefine the show method. This means we need to make sure that the Show method in the parent is virtual, so that we can redefine it in an inherited class.
If you try to compile right now, you’ll notice a problem. The problem is that people is not accessible right now. We don’t want to make people public because we don’t want all users of this class to have the opportunity to damage the data that should remain protected in this class.
The appropriate solution is to make the people variable protected, so that only derived classes can use it. It keeps with the rule of least privilege.
The dependents in HeadOfHousehold need to be made more accessible as well. In this case we need to be able to get the information, but we still don’t want to allow every user to modify the data however they wish. So we use a property with a private set. This allows for public read, but keeps modifications internal to the class.
The TasHouseHold requires some changes to Main to run because the hierarchical relationship for dependents has to be accounted for.
OK, so it looks like we are doing some special processing for dependents now.
It would make a lot more sense to encapsulate that extra Write statement within the Show method for the dependent. Let’s move it there, and while we are at it… let’s show something more meaningful… like the actual relationship of the person.
We need to add a Show method to the Dependent class and override the behavior in the base Person class. In order to make an override, we need to mark the Show method in the base Person class as virtual. We also need another parameter in the dependent constructor to require that the relationship is provided.
Now the Show() method for the dependent provides better functionality and is simpler to use.
Let’s add another child and see the output:
Let’s finish up the special processing of dependents by displaying their age instead of the birth date. Since age calculation has potential benefits to any of the person objects, and we have all the information necessary to perform the calculation in that class it makes sense to define it in the person class and then make use of it from the dependent class.
The dependent Show method changes to one line. It is now completed redefined.
Last Step
As a last step, it is worth mentioning interfaces. Interfaces define the contract that must be adhered to when implementing functionality for a given class. They also make it easier to have segregation between responsibilities for a class. Interfaces do not contain implementation details. They merely include publicly accessible method signatures, and properties.
For example, if you have a method that checks for awesomeness like this then it has limited utility because it doesn’t work for all kinds of people.
We change create an interface and then anything that implements that same interface can be used interchangeably, and for example be passed as a parameter.
Here is the example using the interface. It works now.
That's all I have for today. Thanks for reading. As always, have a fun time.