Full Lectures Set
- C#Lectures - Lecture 1: Primitive Types
- C# Lectures - Lecture 2: Work with text in C#: char, string, StringBuilder, SecureString
- C# Lectures - Lecture 3 Designing Types in C#. Basics You Need to Know About Classes
- C# Lectures - Lecture 4: OOP basics: Abstraction, Encapsulation, Inheritance, Polymorphism by C# example
- C# Lectures - Lecture 5:Events, Delegates, Delegates Chain by C# example
- C# Lectures - Lecture 6: Attributes, Custom attributes in C#
- C# Lectures - Lecture 7: Reflection by C# example
- C# Lectures - Lecture 8: Disaster recovery. Exceptions and error handling by C# example
- C# Lectures - Lecture 9:Lambda expressions
- C# Lectures - Lecture 10: LINQ introduction, LINQ to 0bjects Part 1
Introduction
There is probably million articles about OOP and OOD. You may ask: Why this guy create one more? I have two answers. First of all my article is one of the series where I teach my new coworkers for C# and OOP basics is a requirement for this series. Second reason is that sometimes only specific author is able to explain you some topic with words that is more applicable for you. I hope to be such an author for some people and it will be best reward for me if at least one person will tell to himself: "I didn't know\understand\realize this to mysefl and this author found proper words for me". Explaining such a general topic as OOP in one short article is very difficult task, much more easier to write very specific and technical article. Please don't be very strict about this acticle and if you don't like something, just stop reading and google for another source.
Object oriented programming
"Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which are data structures that contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods." - Wikipedia
Object oriented programming is an approach that uses objects to model the task that software is trying to resolve. Your model is simplified model of real world. You create classes that describe some instances in real world, these classes hold some internal data and have some state. Objects of classes communicate with each other and can perform some data manipulation inside themselves and with external objects. There is so many descriptions of OOP and OOD (object oriented design) that I don't want to invent the wheel and create my own one. I suggest to each programmer to find the best source that satisfy his needs and read about object oriented paradigm. My advice is to read "Domain-Driven Design" by Eric Evans and "Object oriented design" by Grady Booch. I like the way these guys explain the concept and their approach to come to the issue and start implement its software solution. My personal opinion about OOP is that the solution is good when it is simple. I saw dozens of designs that were so complex that it was impossible to extend and support them. The task of architect is to solve problem by simple solutions that are clear to everyone. Scalability and maintenance of such solution should be straightforward and simple. When developers add more and more functionality to solution the whole concept should remain the same and initial architecture should be the cornerstone of the solution. Things above is real and it is possible to do it for any solution. As architect or programmer you should periodically sit and think about the things you can simplify and enhance in your model and solution. Once the process of enhancements is established you will have constant improvements that will keep your software stable, scalable and robust. In this article I would like to concentrate on 4 principles that are treated as main ideas of OOP: abstraction, encapsulation, inheritance and polymorphism and show examples of their implementation in C# code.
Abstraction
Each software product that someone develops has a complexity. Nowadays all software products become more and more complex. Almost every program that you use has cloud server and database. A lot of systems have analytics and statistics engines that are running behind the scene for end user, but they are very important for software provider. All this complexity is designed and implemented by one engineer or, in 99% of cases, by group of people. The task for such developers is to decompose the complexity of the system and split it to small units that are building blocks of your software product. This task is done usually iteratively by different engineering groups (this might be same people playing different roles). First architects do a high level design, then together with developers they do low level design and after the solution is documented programmers start implementation. Of course this is only one example of the flow of complex system development. Nowadays we have a lot of approaches to organize software development process. Bottom-line is that finally you have building blocks for your software that are units from which your system consists. In OOP these building blocks are classes. (Usually after such statements I receive lot of comments that not only classes, not only in OOP , etc.) For simplicity let's agree that we have only classes. Class is the basic building unit from which you build your software. Objects of class can have some data that present their state, class itself can have some data (static members) also both objects and classes have a way to operate with others by using such things as: events, methods, properties, direct access to class members, etc. More about classes in C# you can read in my article here.
When you build your software from classes you encapsulate real world objects to software abstractions. The process of abstraction is the process to build a software model that presents solution for some task\s in real world. Your abstractions are mapped to real object or processes and they present these objects in your software solution. Grady Booch defined abstraction as: "An abstraction denotes the essential characteristics of an object that distinguish it from all other kinds of objects and thus provide crisply defined conceptual boundaries, relative to the perspective of the viewer." Abstraction is actually part of Object Oriented Design (OOD) - the process when you build your model using object oriented technologies.
Let's review an example of abstraction and OOD. Assume we have a farmer, that grows vegetables: tomatoes and cabbage . He had good year, make some extra money and decided to digitalize his farm. He bought controllers that every hour send him signal about current temperature of the land, humidity of the air and humidity of the land. Besides this he has controller to switch on irrigation system remotely. Now this farmer requires a simple software will analyze input from 3 controllers and switch on irrigation if required basing on vegetable type. Let's think about the solution here. We need to build model (abstraction) for this solution. The proposal is following:
- We need a class for each type of vegetable. This class will have a method that receives controllers data and makes decision if irrigation is required and how much water do we need to give for this type of vegetable (for how long irrigation should be executed). As number of vegetables may grow in the future we propose to have an abstract class that all vegetable classes will inherit. This will give us ability to add more vegetable classes easily.
- We need a class IrrigationSystem that will request data from all controllers and check with vegetable class if irrigation should be switched on. If so, this class will switch on irrigation
And here is a sample code that shows a model coded to classes. This is very simple pseudocode example just to demonstrate you how physical world items and processes map to software elements such as classes:
A brief description of how to use the article or code. The class names, the methods and properties, any tricks or tips.
public abstract class Vegetable
{
public virtual bool DoINeedIrrigationNow(float LandTemperature, float AirHumidity, float LandHumidity){return false;}
public void IrrigateMe()
{
}
}
internal class Tomato : Vegetable
{
public override bool DoINeedIrrigationNow(float LandTemperature, float AirHumidity, float LandHumidity)
{
return true;
}
}
internal class Cabbage : Vegetable
{
public override bool DoINeedIrrigationNow(float LandTemperature, float AirHumidity, float LandHumidity)
{
return true;
}
}
internal sealed class IrrigationSystem
{
List<Vegetable> vegetables = new List<Vegetable>();
public void IrrigationProcess()
{
float LandTemp = 0;
float AirHumidity = 0;
float LandHumitity = 0;
foreach (Vegetable item in vegetables)
{
if (item.DoINeedIrrigationNow(LandTemp,AirHumidity, LandHumitity))
{
item.IrrigateMe();
}
}
}
}
Encapsulation
Encapsulation is a kind of continuation of abstraction. Once we decided about the model of our solution and choose what should be implemented and how, we should hide implementation details from end user. Encapsulation hides implementation details and treats them as a secret. Developer defines what should be open in every specific class and makes this data available for user of the class all other details are hidden. Encapsulation is achieved by hiding internal structure of the object as well as implementation of its methods. All that is public is an interface to work with the specific object that is controlled by developer as its creator. As Liskov stated: “For abstraction to work, implementations must be encapsulated”. Encapsulation helps to add rules to abstraction and clarifies it to end user. In terms of classes and object it means that they should be encapsulated by having interface to work with and hidden implementation that is treated as black box. Encapsulation protects your class from unexpected access to its internal structure and corruption of it by changing some state or internal variables in a way you don't expect it. Having your member variables hidden and providing access to your class only via functions gives you ability to implement and test it once and then be sure that your class always behaves properly.
Talking in terms of programming language such as C# we can define following recommendation for encapsulation:
- All data members should be private. You should think twice before declaring some data member as non private
- If you have some methods in class that are used only internally and you don't expect them to be used by class user, you should hide them and make private
- To access data members directly you should use methods or properties.
- Define your properties with the language of your model and don't connect them to names of member variables that they cover. In case if member variables have names that don't feet your model, of course.
- If you write some simple getter\ setter pair, try to think why it happens. Good designs, with some exceptions, don't have flow for access some variables inside object. Everything should be wrapped to functions.
Code below demonstrates a class that presents a simplified model of the cell phone encapsulated to the class:
internal sealed class CellPhone
{
private int m_CellNetworkConnectionQuality; private int m_BatteryPercentage;
public bool CallToPerson(string PersonsCellPhone)
{
AddCallDataToLog();
return true;
}
public bool StopCurrentCall()
{
return true;
}
public bool AnswerInputCall()
{
AddCallDataToLog();
return true;
}
public void GetPhoneStatus(out int NetworkStatus, out int BatteryStatus)
{
NetworkStatus = m_CellNetworkConnectionQuality;
BatteryStatus = m_BatteryPercentage;
}
private void AddCallDataToLog()
{
}
}
Inheritance
In our usual world term inheritance means that item A inherits some characteristics or behavior from item B. Usually item A and item B have some relationship. New model of the car derives a lot of things from previous model, child derives his eyes and hair color from parents, etc. Sometimes as in example of human child-parent inheritance is something we can't control. Sometimes as in example of car models this is the process that is totally controlled by engineer. In OOP inheritance is something you can fully control. There are thousands of definitions of inheritance. I want to provide few of them:
"Inheritance can be defined as the process where one class acquires the properties (methods and fields) of another. With the use of inheritance the information is made manageable in a hierarchical order"
"Inheritance enables you to create new classes that reuse, extend, and modify the behavior that is defined in other classes. The class whose members are inherited is called the base\parent class, and the class that inherits those members is called the derived\child class"
C# supports single inheritance. It means the class can have only one base class. Other languages as for example C++ support multiple inheritance where class can derive several classes. C# designers decide to allow only one base class which at the beginning seems inconvenient for people that came from C++, but finally they got used to stay with it (me is an example).
Inheritance rules:
- You should know that inheritance is transitive. If class A inherits class B and class B inherits class C it means that class A inherits everything from class C as well. This chain in all C# classes works till the parent for all classes in .NET System.Object is reached (more about this in my article here)See below small example that demonstrates this
- As you will see in the sample for statement above I marked last class as sealed. When you use sealed modification for class it restricts this class to be inherited. You will ask what is the reason to do this, I can inherit or cannot, what is the reason for special mark. First reason is to prevent derivation if you don't need it and want to restrict it. Good example of it is base string class (more about string you can learn in my article here). The second reason is productivity. If you're calling a method on a sealed class, and the type is declared at compile time to be that sealed class, the compiler can implement the method call (in most cases) using the call IL instruction instead of the callvirt IL instruction. This is because the method target cannot be overridden. Call eliminates a null check and does a faster vtable lookup than callvirt, since it doesn't have to check virtual tables.
- When one class inherits another it inherits all its and its parents methods and their signatures that were marked as public or protected, internal and internal protected. Public things area available for child class users, protected things are available inside child class only. Even though a derived class inherits the private members of a base class, it cannot access those members.
- Inheritance gives us an ability to use inherited classes in all contexts where base class is used
- Base class may have methods marked as virtual. Word virtual means, that parent class have implementation of the method that may satisfy child class needs. If this doesn't happen child class is free to override this method as it wants. In a virtual method invocation, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke. In a non-virtual method invocation, the compile-time type of the instance is the determining factor
- There is another type of methods that base class may have. These are abstract methods. You must override abstract methods in child class and base class doesn't have its default implementation. Abstract methods can be defined only in abstract classes. Abstract class is the class which you can't instantiate by calling new operator. Abstract classes are classes that can be derived only. They may contain one or more signatures of abstract methods. Derived class may be abstract as well, but if they are not abstract they must implement all abstract functions derived from its parent\s.
- Interfaces is something similar to abstract classes. This is reference type that has only abstract members. When class implements interface it must implement all members defined in interface. Class may implement several interfaces while it can have only one parent class. Using interfaces in C# you can implement multiple inheritance to make sure one class implements set of interfaces you're interesting in.
- Derived class can hide parent class members by defining its own members with same name and signature. You can use new keyword to implicitly point this. Usage of new keyword is not required, but this is good style besides you will receive compile warning if not using new operator. This is called method hiding.
Code below demonstrates things described earlier:
internal class C
{
public void CMethod()
{
Console.WriteLine("I'm a method of class C");
}
}
internal class B : C
{
public void BMethod()
{
Console.WriteLine("I'm a method of class B");
}
}
internal sealed class A : B {
public void AMethod()
{
Console.WriteLine("I'm a method of class A");
}
}
internal class D : B
{
protected void ProtectedMehodInD()
{
Console.WriteLine("I'm protected method of class D");
}
public virtual void VirtualMethodInD()
{
Console.WriteLine("I'm default implementation of virtual method in D");
}
public new void BMethod()
{
Console.WriteLine("I'm a hidden B method of class B and reimplemented in class D");
}
}
internal class E : D
{
public void EMethod()
{
Console.WriteLine("I'm a method of class E ");
ProtectedMehodInD();
}
}
internal class F : D
{
public override void VirtualMethodInD()
{
Console.WriteLine("I'm overridden implementation of virtual method in D");
}
}
internal abstract class AbstractClass
{
public abstract void AbstractFunction();
}
internal interface Interface1
{
void Interface1Function();
}
internal interface Interface2
{
void Interface2Function();
}
internal class AbstractAndInterfaceImplementation : AbstractClass, Interface1, Interface2
{
public override void AbstractFunction()
{
Console.WriteLine("Abstract function implementation");
}
public void Interface1Function()
{
Console.WriteLine("Interface1Function function implementation");
}
public void Interface2Function()
{
Console.WriteLine("Interface2Function function implementation");
}
}
A aVar = new A();
aVar.AMethod(); aVar.BMethod(); aVar.CMethod();
E eVar = new E();
eVar.CMethod(); eVar.EMethod();
C cVar = new A();
cVar.CMethod(); cVar = new B();
cVar.CMethod();
D dVar = new D();
dVar.BMethod();
dVar.VirtualMethodInD(); dVar = new E();
dVar.VirtualMethodInD(); dVar = new F();
dVar.VirtualMethodInD();
AbstractAndInterfaceImplementation classVar = new AbstractAndInterfaceImplementation();
AbstractClass abstractVar = classVar;
abstractVar.AbstractFunction(); Interface1 interface1Var = classVar;
interface1Var.Interface1Function(); Interface2 interface2Var = classVar;
interface2Var.Interface2Function();
Polymorphism
Polymorphism is realized using inheritance techniques, but it is treated separately as the one of the stones of OOP. As previous terms that we've discussed during this article, polymorphism has thousands of definitions that mean the same thing, but described differently. I wat to show you some definitions of polymorphism:
"Polymorphism is the ability of an object to take on many forms. The most common use of polymorphism in OOP occurs when a parent class reference is used to refer to a child class object."
http://www.tutorialspoint.com/
" Polymorphism is a concept in type theory wherein a name may denote instances of many different classes as long as they are related by some common superclass. Any object denoted by this name is thus able to respond to some common set of operations in different ways. With polymorphism, an operation can be implemented differently by the classes in the hierarchy."
Grady Booch
Polymorphism can be static when we know which type is used at compile time and dynamic when types that are used for actual work are defined at runtime. Dynamic polymorphism is much more flexible and actually while someone talks about it, he means dynamic usually. My personal definition of polymorphism is: "ability to decide about implementation dynamically while runtime". If you have some flow that finishes with specific functionality that may be implemented differently, polymorphism is a best way to do it. From my perspective good example of polymorphism is architecture that includes different plugins. Core functionality provides some interfaces that 3rd party may implement differently and than this functionality is loaded to core as plugins.
In C# static polymorphism is achieved by function or operator overloading. Dynamic polymorphism in C# is realized using virtual functions. Base classes may define and implement virtual methods, and derived classes in their turn can override them. When derived class overrides the function it means that this class provides its own definition and implementation. At run-time, when client code calls the virtual method, the CLR looks up the run-time type of the object, and invokes proper implementation of the method.
Code below demonstrates primitive polymorphism example:
internal class CBaseShape
{
public virtual void PaintMyself()
{
Console.WriteLine("I'm default implementation and don't paint anything");
}
}
internal class Rhombus : CBaseShape
{
public override void PaintMyself()
{
Console.WriteLine(" *");
Console.WriteLine(" ***");
Console.WriteLine(" *****");
Console.WriteLine(" ***");
Console.WriteLine(" *");
}
}
internal class Square : CBaseShape
{
public override void PaintMyself()
{
Console.WriteLine(" ****");
Console.WriteLine(" ****");
Console.WriteLine(" ****");
Console.WriteLine(" ****");
}
}
internal class Rectangle : CBaseShape
{
public override void PaintMyself()
{
Console.WriteLine(" ********");
Console.WriteLine(" ********");
Console.WriteLine(" ********");
}
}
bool bExit = true;
Rectangle rect = new Rectangle();
Rhombus romb = new Rhombus();
Square sqr = new Square();
while (bExit)
{
CBaseShape bs = new CBaseShape();
Console.WriteLine(@"Type your choice or type 'exit' to stop");
Console.WriteLine(@"Reminding you can see behavior of following figures: rhombus, square, rectangle");
string line = Console.ReadLine();
if (line == "exit") {
break;
}
switch (line)
{
case "rhombus":
bs = romb;
break;
case "square":
bs = sqr;
break;
case "rectangle":
bs = rect;
break;
default:
break; }
bs.PaintMyself();
}
Using new vs override:
This section I've added to article after you guys read it thousands of times and I received several suggestions of improvement. One of suggestions was to show polymorphism little bit differently and another one to give explanations when we should use new and when override and what is the difference.
Let's review following code that demonstrates polymorphism example with classes from previous section but it executes their usage differently:
CBaseShape[] t = new CBaseShape[3];
t[0] = new Rhombus();
t[1] = new Rectangle();
t[2] = new Square();
for (int i = 0; i < t.Length; i++)
{
t[i].PaintMyself();
}
In this code sample we call PaintMyself function in the loop and use base class object for this purpose. Compiler doesn't know which method call will be called at every iteration and in links proper object dynamically. Now let's change class Rectangle a little bit and replace word override by word new in PaintMyself implementation. When we build and execute again result will be that for Rhombus and Square we have call of their implementations, but for Rectangle default implementation is called. This happens because with new operator class Rectangle doesn't implement virtual function of CBaseShape and compiler doesn't have any choice besides calling default implementation. Basing on it, you should understand the difference between new and override and be very carefull with it's usage when you have polymorphism usage in your application. I've updated source files attached to this project with this example and you can play with it.
P.S. Pawel, thanks for great input for my article.
Sources:
- "OBJECT-ORIENTED ANALYSIS AND DESIGN With applications" by Grady Booch
- "Domain-Driven Design: Tackling Complexity in the Heart of Software" by Eric Evans
- https://en.wikipedia.org/wiki/Object-oriented_programming
- Jeffrey Richter - CLR via C#
- Andrew Troelsen - Pro C# 5.0 and the .NET 4.5 Framework
- https://msdn.microsoft.com