Click here to Skip to main content
15,867,594 members
Articles / OOP

Introduction to Object Oriented Programming (OOP)

Rate me:
Please Sign up or sign in to vote.
4.92/5 (53 votes)
7 Apr 2014CPOL46 min read 108.6K   77   51
This is a beginner's article that explains Object Oriented Programming (also known as OOP or OO programming) by my personal view and experience with it.

Before Starting

I wrote this article trying to explain Object Oriented Programming to people that actually don't know anything about the topic and I hope I am making it simple enough to be understandable.

Yet, I really expect that readers of this article know the basic about programming, like what's a variable, what's an array, loops and the idea of function calls (also known as procedures and methods).

Background

I started to program computers when I was 11 years old (maybe 10 and a half, I am not really sure) using AMOS Basic. That language was focused in game development and was very simple (or, as some say, very limited). Such simplicity helped me better learn the programming constructs, like ifs, elses, the logical operators used on ifs, gotos, function calls etc. Also, by its limitations in "object allocations" (as it didn't really support object allocation) I learned how to use different arrays kept in sync to deal with grouping of information and how to reuse empty places in arrays for new items.

Some of the limitations of such language made me think about better solutions... and when I found the C language I found many of those better solutions. I spent some time trying to learn assembler after that (my first try wasn't successful), later I learned C++ (at the first look was terrible, but this is a flaw of the C++ libraries, not of the language) and, when I understood C++ I loved it even more. It had even more solutions for those problems that I was still finding in C. Actually, everything that I was looking for was related to Object Oriented Programming and, as I still see people misusing and misunderstanding OOP and I also receive many requests to explain OOP to new developers I decided to write this article.

Note: It is a personal view on OOP and a personal explanation on how I learned things. I will not be focusing on the "correct definitions" as, by the most basic definition, assembler can be an OOP language as we can create objects in assembler (and, in the end, any program can be seen as an assembler program).

First steps

When I started to program computers, I was using a language that didn't support objects at all. It only knew 3 data-types. Integer numbers (also known as whole numbers [1, 2, 3 are examples of integer numbers]), real numbers [like 3.14] and strings ["a text that can be presented to the user"]) and arrays of any of those 3 types.

The type of a variable was encoded in its name (a string variable had a name that ended with a $, so name$ meant a string), an integer didn't had any special character and real numbers used a # postfix (so, Price# will be a real variable).

I am not going to discuss naming rules. The only thing that matters is that by having only 3 kinds of data types it helped me understand and focus only on the programming keywords (like if, for, goto, gosub [or function calls] etc) and it also helped me understand how to built algorithms that reutilise an specific item in memory that was dead (actually, a dead enemy in a game) to put another one in its place as it didn't allowed me to "deallocate" memory.

Yet the fact that I could only use one of those 3 data-types or an array of those 3 data-types was something that bothered me. I actually created many functions to try to "automate" that repetitive work of allocation, deallocation and of "object construction". To exemplify, considering the language was used to create games and considering we can easily have many "enemies" on the screen, each one with its own X/Y position on the screen, it was not uncommon to have arrays like:

  • EnemiesX(someSize);
  • EnemiesY(someSize);
  • EnemiesKind(someSize);
  • etc.

And so, if I wanted to execute the same action over all the enemies I actually needed to:

  • Know that "someSize" value. I could extract it from any of the arrays but it was my responsibility to keep them in sync, to make them have an acceptable size to support all the enemies that could appear on the screen and even to know when an enemy was dead (maybe another array to tell that);
  • I was forced to access all of those arrays in the same index to get all the information of a single enemy.

This may look silly, but I will try to explain it with rectangles. Imagine that you simply want to store a variable number of rectangles with their colors in memory and at some moment draw them on the screen.

You would actually need to have 5 arrays (left, top, width, height and color [we could actually store right and bottom instead of width and height, but this is not the important factor now]).

Imagine the function that draws all rectangles. It could look like this:

For I=0 To ArrayLength(RectanglesLeft)-1
  DrawRectangle RectanglesLeft(I), RectanglesTop(I), RectanglesWidth(I), RectanglesHeight(I), RectanglesColors(I)
Next
// This is a pseudo-basic code.

Wouldn't it be better if we could create a new "data-type" that had all those traits?

For example, a Rectangle type with Left, Top, Width, Height and Color so we could simply have an array of Rectangles?

This could simplify the code to something like:

For I=0 To ArrayLength(Rectangles)-1
  r = Rectangles(I);
  DrawRectangle r.Left, r.Top, r.Width, r.Height, r.Color
Next
// This is a pseudo-basic code.

Or, even better, the DrawRectangle could actually receive a Rectangle, so:

For I=0 To ArrayLength(Rectangles)-1
  DrawRectangle Rectangles(I)
Next
// This is a pseudo-basic code.

The advantage here is that we have a single array, so there's no trouble for accessing them all and it is impossible to make them get out of sync (this can happen if you resize the first and second array and there's an out-of-memory when resizing the third one).

With the Rectangle type it is guaranteed that all the traits will be there and, when calling another function, we don't need to pass 5 parameters, we can pass a single parameter of the right type. When dealing with the array directly we only need to deal with one array. Resizing it is also a single operation, so it either succeeds or fails entirely, without in-the-middle situations.

So, being able to create this Rectangle type is our first step towards OOP (it actually was my first step towards OOP).

C Structs

C allowed me to put all the traits that I wanted in a single array of a struct. Such struct could be given a name (Rectangle in this case) and it could have all the traits that I needed (the left, top, width, height and color).

In a game (remember, I started with games) I needed traits like the X/Y position of the enemy in the screen, the direction that he was looking at, the speed that he moved, the kind of enemy etc. And C allowed me to create a single array of the right type (Enemy for the new examples).

But even being able to create those structs and then being able to create many objects of that struct, C is not considered an Object Oriented Programming language.

This happens because even if we can say that those enemies are objects, the language doesn't support all the features that are the base to OOP (or at least doesn't help using them). In fact OOP has some confusing definitions and by the most basic ones C is object oriented. Still the most important traits of OOP aren't enforced by the language, and those are Encapsulation and Polymorphism.

Note: a struct, a class and an interface are called "types" (of objects). They actually say which traits exist in an object of its type. An object is the actual "data" of a given type. For example, the Enemy type defines that it has the X and Y position, Direction and others. Each enemy, then, is an object that have those traits.

Encapsulation

Encapsulation actually means two things. One of them is grouping of "members" and the other is hiding of "members".

For example, when talking about the rectangle I was talking about left, top, width, height and color, and I grouped all that data in a single Rectangle type, which we can achieve by using C structs.

But what about grouping the functions that we can use with a rectangle?

The DrawRectangle, for example, doesn't need to be a "global" function that receives a Rectangle. It can be a function "inside" a Rectangle. In other words, we can have a Rectangle type, with the traits (left, top, width, height and color) and the functions (called methods, in OOP, like Draw). So, we could draw a rectangle by doing rectangle.Draw instead of DrawRectangle rectangle.

This actually make things simpler to organize, as instead of needing to have DrawCircle, DrawRectangle, DrawImage etc, we only need to have Draw methods, which will of course be related to the actual type (so, we expect a rectangle.Draw to draw a rectangle and a circle.Draw to draw a circle).

So, being able to group the functions together with the object definition is something that differentiates OOP languages from non-OOP languages and C can't put functions "inside" the new types (some people may argue that function pointers do the same, but they aren't quite the same as they occupy space "per object"... they are actually a way to achieve OOP in C, even being harder to use than their C++ counterparts).

Hiding

About the other trait of encapsulation, which is used to hide implementation details of a class, it is both very important and also very problematic. It is problematic because a lot of people avoid it because they say "it is useless to create methods to get or set a variable if I can do that directly by making the field public".

To explain the entire situation, I said that we could use the traits Width or Right in a Rectangle. In fact, to have Left, Right and Width we only need to have 2 traits in the object's memory, and the other one can be calculated. The same applies to Top, Bottom and Height.

Should we store all traits in the object? Should we always recalculate some of them to use less memory?

If we have all traits in the object, how do we guarantee that when setting Left and Right users will not forget to set the Width trait?

If later we decide that we will have only Left and Width in memory, calculating the Right, how would we guarantee that users will not try to set the Right, after all it doesn't really exist?

The answer is: Users of the class should not know anything about how the data is stored in memory. Only the writer of the class should know which data is really stored in memory.

This kind of thinking actually creates a Principle in which we never expose fields (the actual traits that consume memory per object) and so we should always create functions (or better, methods, as this is the name of functions inside types) or properties that will get or set the needed values.

That is, if users have the option to call getLeft, getRight and getWidth methods, it is not important which one of them is calculated (if any of them is calculated on reads). Also, when changing the values, users will be able to call methods like setLeft, setWidth or setRight, which can actually validate the new value and recalculate the other fields if required (so, if the Width is calculated, a call to setWidth can verify that the new value is not less than zero and then set the right field as left + new width value, but the user of the class doesn't need to know about this calculation).

And, to complete this job, the fields that will exist in the class must become completely inaccessible to other classes. The other classes should only see those methods, so they can get all the info they need through them, without knowing the inner details of the class.

C++ and JavaScript

In the sense of not caring about the internal details of a class made by someone else C++ mostly follows OOP. It has the visibility modifiers (things like public and private) that inform which traits can be seen and manipulated by other classes and which traits are accessible only by the class itself. Yet, when we deal with compilation C++ has a problem with the size of the "internal details" as, even if other classes can't access the traits marked as private, when creating a new object instance the actual size of those "inaccessible" traits is important. That's not a problem if we recompile the class that's being instantiated together with the code that creates new instances of the class, but it causes serious problems if the class (like Rectangle) lives in a DLL and the code that creates the Rectangle instances/objects lives in another application. If, for some reason, the size of the object in the DLL changes, the application that uses it must be recompiled or crashes will happen at run-time. In this sense, even with the capacity to hide members, C++ isn't really allowing the implementation details to change. C# and Java do allow this.

And about JavaScript, well, it is actually considered Object Oriented. It doesn't use the well known terms like public and private for encapsulation and considering that it is never compiled as a DLL to be used later, it avoids the problems of "object resizes" that could happen in C++ when dealing with possibly recompiled DLLs. But the fact that it doesn't have clear visibility modifiers, in my opinion, means that we can achieve OOP in JavaScript, the same way we can achieve OOP in C, yet it is not Object Oriented as a language. Some people will still disagree with me, though, after all JavaScript is considered an Object Oriented language. How dare I say the opposite?

Traits, properties, fields and members

I used the terms traits, properties, fields and also members.

  • Trait is not a term used in programming. It was my decision to use this term exactly to avoid confusion with existing terms and I was only talking about a characteristic that exist in an object, independently of how it works;
  • Field is the name used for those traits that are really part of an object and consume memory. Getting and setting a field does a direct memory operation and can't execute validation code or change other fields;
  • Property is used to talk about those traits that are usually composed of get/set methods. For example, the Left property can be composed by getLeft and setLeft methods in languages that don't support real properties. In those languages that support properties we will use them like fields, but they can be implemented by methods, so they have the possibility to calculate values on gets and do validations and change many fields on sets;
  • Member is actually any field, method or even property in a type. But a member is only one that really exists in a type so, for languages that don't support real properties, the getLeft and setLeft will be members, but the Left property will not, as it doesn't really exist, it is only a name given by developers to refer to the get and set methods.

Polymorphism

I "love" this term and also its basic explanation (to let it clear, I actually hate this term and its basic explanation): "Polymorphism is the capacity of an object to assume different forms".

"So, if I have a Shape object with an IsEllipse property that is used to determine if it is drawn as an Ellipse or as a Rectangle and I can change it at any time, this is polymorphism, right? After all, the shape will be able to change from Rectangle to Ellipse and back to Rectangle at any time."

The worst is that I actually said that to a teacher in highschool and he said that I was right. But polymorphism has nothing to do with an object changing forms. In fact, the object doesn't need to have any visual representation to be polymorphic.

Polymorphism is the capacity to do "the same call" but to have different results (different code being executed) thanks to some kind of context.

For example, do you remember that I talked about putting a Draw method inside the Rectangle type?

Now imagine that there's another type, called Line. It has the initial and final coordinates of the line to be presented in the screen and it will also have a Draw method.

So, if we have something like this:

Function DrawAll(Array)
  For Each Object in the Array
    Object.Draw
  Next
End Function
// The For Each will simply execute the same inner block to every object in the Array.

We will be able to Draw a Rectangle as a rectangle and to Draw a Line as a line. We will not need to call different methods (like DrawLine and DrawRectangle) and we will not need to check for the object type before doing the call, so the Draw call is actually Polymorphic. In fact, such example is run-time and dynamic polymorphism. It is dynamic because the DrawAll function has no idea about the types of items that will exist in the array. It only knows that it will be an array and it believes it can call Draw on the items of such an array.

Such dynamic behavior is many times seen as problematic as it can cause run-time bugs. What will happen if I pass an array of numbers, which don't have a Draw function? It will compile fine, as there's no validation on the input parameters, but it will fail during execution.

C++, C#, Java (not JavaScript) and many other languages are mostly statically typed languages. That is, a type must be known at compile time. So, I could (and in fact I should) write things differently. One of the possibilities (not yet the best one) would be this:

Function DrawAll(Array of Rectangles)
  For Each Rectangle in the Array
    Rectangle.Draw()
  Next
End Function
Function DrawAll(Array of Lines)
  WriteText "I should be drawing lines but, instead, I decided to write this text"
End Function

In this case, I can't simply call DrawAll giving "any" array. I must call DrawAll giving either an array of Rectangles or an array of Lines.

At compile-time, by the type of the array, the compiler will choose which one of the DrawAll functions to call. This is the kind of polymorphism that exist only to help developers. It is not really different from having a DrawAllRectangles and DrawAllLines functions but we (developers) don't have to use different names when doing the call. The type of the arrays will decide the right method for us.

Then, there's the run-time polymorphism that exist in C++, C#, Java and other languages that is not as dynamic as the first one. We will write a single DrawAll function expecting an array of some abstract class or interface.

That is, our DrawAll function could look like this:

Function DrawAll(Array of Shapes)
  For Each Shape in the Array
    Shape.Draw()
  Next
End Function

And both Rectangle and Ellipse (and any other shape that has a Draw method) should inherit/implement the Shape class/interface.

In such class or interface there will be the declaration of a Draw method, without actually having any code for it. This is useful for a situation like this one, so the DrawAll method can receive any Shape and know that it can call a Draw method, as it should exist. At compile-time it will be possible to validate that a Shape will have a Draw method, so it is possible to call such a method for Rectangles and Lines, but it is not possible to even try to give an array of ints to the DrawAll function, as ints aren't Shapes, causing a compile-time error (which will forbid the developer to publish its program, also avoiding failures when clients actually try to run the code). Yet, at compile-time we don't need to know which code will be actually called (will it be a Rectangle.Draw or a Line.Draw or some other shape sub-class.Draw?).

Non-visual polymorphism

I know that a Rectangle is a visual shape and so, even if I said that polymorphism has nothing to do with "forms", I am still giving a "visual" example.

So, let's see (or better, only read) another example. In the programming world we deal a lot with Streams. But to make it even simpler, let's talk about Writeables.

As a developer you can create a function that "writes some text" (like the date and time) to... an Writeable.

Such Writeable doesn't need to have a visual representation. Yet, it can be Polymorphic, doing its work by:

  • Writing to text file;
  • Sending data through a TCP/IP connection (maybe sending an e-mail);
  • Storing the text in memory;
  • Sending the text to the screen (ok, this one is visual);
  • Doing nothing.

Is it strange to create writeables? Especially one that does nothing?

Well, this is used very frequently in logging algorithms. You write code that does some work and also generates some logging, but the actual logging goes to some Writeable, which may be saving to a file, may be writing things to the screen, may be registering things in a "memory stream" so another task will show it accordingly... or may be doing nothing, so the code that generates the log can call the Write method everytime, even if we don't want logging.

A different presentation

I tried to be brief and at the same time to explain all traits of Polymorphism and Encapsulation. I know that those concepts are hard to get without good examples, so I will try to present a better example, even if it doesn't cover all the details of those topics.

I was talking about games and I commented about enemies. Let's imagine it is a space shoot em up game, that we actually use lists to store the enemies that are shown in the screen (lists are like "redimensionable" arrays) and that we have 3 kinds of enemies.

  • The basic enemies simply go down the screen. They can do it in diagonal, but they will not change their direction and they will inevitably go off-screen;
  • The intermediate enemies go down in zig-zag, so they will also get off-screen;
  • And the hard enemies keep moving in the screen until they die or until they kill you, so they are not going to disappear by going off-screen.

As we all know, an animation is done by many similar (yet a little different) frames that are presented successively so, in a game, we may use a frame-by-frame based calculation.

So, before checking if there is any collision (which can cause an enemy or the player character to die), we will first move every enemy.

The algorithm to animate all enemies can be like this:

For Each enemy in enemiesList
  MoveSingleFrame(enemy)
Next

It can also be like this:

For Each enemy in enemiesList
  enemy.MoveSingleFrame
Next

The first case is more similar to how it will look in C. The second case actually has better Encapsulation, but it may or may not use Polymorphism.

Even if reading both blocks of code are pretty similar, writing the second one can be much easier with the right editors. With the modern editors we can see a list of the available functions/methods in a given context and, in the first case, we may end-up seeing lots of functions that aren't related to what we want to do. In the second case, by writing enemy. we will be able to see only the functions that are part of the Enemy class. So, it may be actually easier to write code using the second version.

It is also better encapsulation as the MoveSingleFrame will not be a "global" function, it will be contained in an Enemy class.

Independently of the Encapsulation part, both solutions may not use Polymorphism (and they can actually generate exactly the same code when compiled). So, how is the MoveSingleFrame implemented?

Without Polymorphism we may have the Kind of an enemy instance as a number (int).

So:

  1. Basic enemy;
  2. Intermediate enemy;
  3. Hard enemy.

And we can implement the MoveSingleFrame like this:

Function MoveSingleFrame(Enemy)
  If Enemy.Kind = 1
    BasicEnemyFrame(Enemy)
  ElseIf Enemy.Kind = 2
    IntermediateEnemyFrame(Enemy)
  ElseIf Enemy.Kind = 3
    HardEnemyFrame(Enemy)
  Else
    GenerateErrorAsThisKindOfEnemyShouldNotExist
  End If
End Function

Do you see a problem with this approach?

Actually there are many. For example, if we create new enemy kinds we will need to revisit this function to add new tests.

If we have a really big game, with more than 1000 kinds of enemies, we will need to put 1000 conditions in a single function. Also, considering there are going to exist other functions (like the damage calculation) that can be related to the kind of enemy we will have to keep all the functions "in sync" having those 1000 conditions, and we will have to remember the number of each enemy kind. Even worse, we will have a very slow algorithm if we need to animate an enemy of kind 1000, as 999 tests will be done before actually reaching at the appropriate condition.

So, looking for possible improvements, we can have:

  • Enums. Enums are actually a way to put a name to a value. So, instead of testing if the Kind equals 1, 2, 3... or 1000, we will test if the Kind equals BasicEnemy, IntermediateEnemy, HardEnemy... or LastBoss;
  • Switches. Instead of using If, and many, many ElseIf we can use switches. Switches are actually optimized to check many conditions over a single value, being able to do bit tests (so, only 10 tests for 1000 possible values) or using jump tables (which will check the minimum and maximum values and, if everything is ok, will use an internal array of jumps... this is an implementation detail of the compiler itself, so only keep in mind that switches are meant to be fast);
  • Use some kind of virtual/dynamic dispatch. This is Polymorphism and this means that instead testing the Kind on the MoveSingleFrame to then call the appropriate EnemyFrame, the MoveSingleFrame will be actually the correct one for the object being manipulated.

Making use of Polymorphism

To use Polymorphism, instead of putting a Kind inside a single Enemy class we will have the Enemy as a base class, only saying that it will have a MoveSingleFrame method, then each Kind of enemy will be a sub-class that implements the MoveSingleFrame differently, with the actual EnemyFrame code.

So, it is the class that represents the Kind, not a numeric value, and the call to MoveSingleFrame will jump to the correct code based on the actual Enemy sub-class instance we are working with.

In source code, we gain the following advantages:

  • Each class can live in its own file;
  • Each class only needs to know what's related to its own Kind of enemy, so the MoveSingleFrame will be very small;
  • The other methods that may be needed (like DetectCollisionAndCauseDamage) will be near each other by the enemy kind. That is, in the BasicEnemy class you will be able to see both the MoveSingleFrame and the DetectCollisionAndCauseDamage that are related. It is differently than seeing a MoveSingleFrame inside a function that has 1000 conditions and then having to search for the appropriate enemy kind on the DetectCollisionAndCauseDamage;
  • New enemy kinds can be added by creating new sub-classes of Enemy, without having to change any of the existing classes. When we had those if or switches by Kind, adding a new enemy kind meant that we needed to change all the methods that do a switch or if by Kind to check for the new one;
  • Actually, in applications that load libraries dynamically, it is possible to create new Enemy kinds in different libraries and load them without problems (imagine that the level definition is in a text file and then the text says which enemies to load). This is simply impossible with a code that needs to know all the existing kinds to chose the right method to call.

The programmer side of OOP

When I first talked about Encapsulation I said that the support for private and public members is the part of encapsulation related to hiding implementation details.

Well, the support for some features (like Encapsulation and Polymorphism) is what is usually used to determine if a language is object oriented or not. But such simplification may cause problems, as C is actually able to hide implementation details if the programmers know how to use it.

Another way of seeing it is this: If a language supports the OOP features and make them easy to use (usually by having specific keywords that make their usage pretty simple) then it is an OOP language. This actually doesn't mean every application created in an OOP language will be written in an object oriented way. We expect that it will be simple to use it, but this will not happen automatically, by simply using the language.

For example, we can still use switches and only primitives data-types in C#, effectively programming as if it was a structured language. It is also possible to do the opposite and to program in an OOP way even on languages that aren't considered OOP. For example, every code ends-up compiled to machine code and it is simple to translate a binary representation (assembly) to a text representation of that machine code (assembler). Assembler is not OOP but it surely allows OOP code to be written.

So, independently if our language is officially considered an OOP language or not, it is our responsibility to make sure that our programs are really using OOP. This actually created many "principles" to create good OOP programs.

Those principles include:

  • Never, ever, create public fields. Always expose traits by methods or properties;
  • * If using C, C++ or any language that may be bound to the object size in memory, which can break client code, either create the types with some reserved fields for future use or create functions like Create/Destroy for your types. Client code that uses the Create/Delete methods will not break if the size of your objects is changed in the future, as the Create code will be in your library and will surely be recompiled together with the object that changed in size;
  • Never create global functions. If needed, create static methods inside a class so you can at least have some kind of grouping (if you are actually using a language that doesn't support classes or namespaces you will need to always prefix all your functions with the type to which they are related to);
  • If you have a Type or Kind trait and you use an if or switch to take different actions based on it, think about creating an abstract class or interface to represent the kind and putting each condition code in a different sub-class;
  • * If the switch is used over some external value (like a text character or an enum value provided by another library) it is still possible to replace a switch with a mapping/dictionary between the input values and instances that implement an interface/abstract class.

And, well, there are many other principles that were built on top of OOP. Some of them became so popular that actually some people call them OOP principles. This include things like:

  • Program to interfaces, not to implementations;
  • Prefer composition over inheritance;
  • SOLID (which is an acronym for 5 principles);
  • Many design patterns like the the (abstract) factory pattern that says that we should not create our object instances directly, we should depend on a "service" that will do the job for us.

Interfaces and abstract classes

I already presented that an abstract class may have a definition of a method without an implementation and this is useful so you can create yet another method that will be able to call it, like a logger method that calls writeable.Write when the actual Write could be implemented in many different sub-classes.

I also said "abstract classes or interfaces" in many situations. So, what's the difference between them?

Well, the difference doesn't exist in all languages and it is more conceptual than a need. Abstract classes are actually classes that have at least one abstract method, but they can have other non-abstract methods and fields.

Interfaces are like abstract classes, but all their methods must be abstract and they don't allow any fields;

C++, for example, doesn't have real interfaces yet many developers create classes as fully abstract and call them interfaces.

C# (the .NET in general) and Java allow us to create classes marked as abstract even if they don't have a single abstract method... this is actually a way to force users to inherit the class and some developers will say that those classes use the abstract keyword but aren't really abstract.

Also, .NET and Java don't support multiple-inheritance, so using a fully abstract class as base doesn't allow a class to inherit from another base class, but they allow a class to implement many interfaces. Implementing many unrelated interfaces in a class is usually a bad practice but some developers are capable to find uses for this trait.

I am actually of the opinion that good code (good class/object) do what it needs to do without caring about sub-classing or interface implementations. It should only inherit or implement an interface if its purpose is to do that. So, on those codes that don't implement interfaces or base classes, if they must be "seen" by code that requires an interface or base class, we create another class that inherits that base class or implements the needed interface(s) and redirects to the code that works without doing such implementation (actually such class that implements the interface and redirects the calls is called an adapter). Only in performance critical code or to use those bad frameworks that can't deal with adapters we may actually create our code that "works by itself" and still make it inherit a base class or interface so it executes faster or is directly compatible with another framework.

Using Encapsulation

Before I explained what is Encapsulation. Now I want to present how to use it.

Most languages utilise 3 keywords for Encapsulation: public, protected and private. These are also the visibility modifiers used in UML class diagrams.

public and private are the easiest ones to understand. A public member is accessible to everyone. A private member is accessible only to the class itself.

protected is a particular case. It is accessible by the class itself and by any of its sub-classes, but it is not accessible to other classes. In most frameworks the methods that are polymorphic are usually the protected ones. They are there to be (re-)implemented, but only the framework itself is expected to call them, so they aren't public.

Actually these 3 keywords miss two situations . As I just said, most frameworks use protected methods as the polymorphic ones, but:

  1. They should be (re-)implemented only, not called. The protected keyword allows sub-classes to (re-)implement methods (implementing or reimplementing a method is called overriding) and also allow the protected methods to be called, even if this is not what the framework wants to allow;
  2. Usually the framework class that calls those protected methods is manager class, which is not the class that declares those protected methods or a sub-class it. So protected is not enough.

Actually I don't know any language that solves the number 1. It is simply a rule that protected methods meant to be overriden should not be called by the sub-classes.

For the number two each language creates a different way to solve the problem. UML purists consider this breaking Encapsulation but I actually consider it as a correction to a missing feature in the visibility modifiers.

C++ allows a class to say which classes are "its friends". Those friends are capable of accessing all the members of the former class, independently of the visibility modifier (OK, C++ is really breaking Encapsulation).

C# has two extra visibility modifiers, named internal and internal protected (or protected internal, the order doesn't affect the result). internal members are considered public to other classes compiled in the same executable or DLL, but are considered private to code compiled in other executable or DLL.

internal protected is accessible from sub-classes independently from which assembly they come from and are also accessible to other classes compiled in the same executable or DLL in which the member is declared, but they aren't accessible from non sub-classes declared in other executables or DLLs.

Personally I consider the C++ friend class better than internal, even if it breaks Encapsulation, as in many projects people put all classes in the same executable, so the internal ends-up working as public, but I would prefer it even more if languages used a combination of friend class and internal, in which only the internal members (not all members) could be exposed to friend classes, not to all classes of the executable or DLL. But I don't know any language that works like this.

JavaScript doesn't have visibility modifiers. All members put into an object are public (I say put into an object because we don't declare which members exist in a class, we can add members into an object at any time). We can achieve a kind of private members because a function can declare yet another function and put it into an object, and this new function has access to the local variables of the first function. These local variables aren't accessible to external code and, so, they work as private members, even if they aren't really inside the object. If you think this is complicated, well, this is why I don't consider JavaScript to be really Object Oriented. It has objects, it has Encapsulation, but it is not easy to use.

Finally, JavaScript doesn't have protected members, so any member that is meant to be polymorphic must be public. I should add that by the way JavaScript works all members are actually polymorphic (you can always replace them... at any time, by any class) but this doesn't mean that those members are expected to be polymorphic and overriding those members that aren't expected to be polymorphic may actually be a source of bugs.

C doesn't have visibility modifiers but actually the C code is split into declarations (usually in a .h file) and implementation (usually in a .c file). So, by not giving a header to a struct you don't want users to have access to, you make that struct work as if it was internal (to compare to C#).

In fact, considering that we should not expose the internal details of a class and structs in C can only hold internal details, this means that we should never give the headers to the structs. Then, it is up to us to create the functions that will create the objects, destroy them and also manipulate them (working as the properties or methods) and to put them into a public header file. So, those functions that aren't in a public .h file are private and the ones in the public .h file are public. This means that we don't have protected members.

It is actually possible to create them, but it becomes so hard that I will not try to explain it, as my purpose is not to teach how to achieve OOP in C.

Namespaces

Namespaces are part of encapsulation. We can say they fit in the grouping category but, well, all groupings also help hiding information that should not be seen.

In .NET and using Visual Studio, for example, when we write System.Collections. we will see all the types and sub-namespaces found in the System.Collections namespace. That is, it will not be showing the classes from other namespaces (a kind of "hiding", even if we still have access to use the other namespaces) and it surely shows that there's a grouping of classes related to collections (arrays, lists, etc are all "collections of objects").

They are also useful to avoid ambiguous situations. For example, a Rectangle may be an in-memory representation of coordinates only (so, the Color will not be part of it) and it may be a class that represents a visual Rectangle, which may include border color, border size, border style (dotted, solid etc) and, of course, its inner color or filling. Both classes can exist which the same name, as long as they are grouped in different namespaces.

Using Polymorphism

There are inumerous ways to use Polymorphism. As I already said, JavaScript is always polymorphic (and you don't have the option to disable such polymorphism).

In C the function pointers allow Polymorphism, even without the support for classes (and they are the base to simulate classes if you want to use OOP in C). In C# we can use delegates (similar, but a little more complete than function pointers) and virtual methods.

Well, the virtual methods are what most people think when we talk about Object Oriented Programming and Polymorphism, so I will focus in them.

To use virtual methods in C++ and C# we must mark the methods with the virtual modifier. In Java all methods are virtual by default and we can mark them as final to remove such polymorphism. Abstract methods are always virtual, but they don't come with a default implementation.

To try to explain what is the difference of a polymorphic (or virtual) method and a non-virtual one I think it is good to have examples.

So, imagine that I have the Rectangle class and a Line class. Both with a non-virtual Draw method.

We can already do things like:

C#
Rectangle rectangle = new Rectangle(0, 0, 100, 100, 0xFF0000)
rectangle.Draw();
// This sample code is in C#.

And:

C#
Line line = new Line(0, 0, 100, 100, 0xFF0000);
line.Draw();

Those calls aren't polymorphic because the compiler knows the real type of the rectangle and line variables, so the calls are pretty equivalent to these:

C#
DrawRectangle(rectangle);
DrawLine(line);

This is different from those DrawAll methods that receive an array (be it an untyped array or an array of shapes). In those cases, the code knows that it will receive some objects and will call Draw on them, but it doesn't know which code is actually being executed (is it Rectangle.Draw? Line.Draw?) and every object can have a different Draw (we can put Rectangles, Lines and others in the same array).

But C++, C#, Java (again, do not include JavaScript) and other statically typed languages require a type to be known at compile time, and this type must already have the method we want to call (so the compiler can validate at compile-time that such call has the appropriate parameters). This is why in those languages we will need to use a base class or interface. We must have a type with a Draw method that both Line and Rectangle can use, and it is not logical to make a Line be a sub-class of Rectangle or vice-versa, as they are simply different classes.

So, the developer is forced to create some kind of base type (class or interface) and put all the methods that are to be polymorphic in it.

In my case, I will use this class:

C#
public abstract class Shape
{
  public abstract void Draw();
}

Then we make both Rectangle and Line sub-class the Shape class. In C# we will also need to be explicit about virtual method implementations, so we should use the override keyword to say that we are implementing the Draw method first declared in Shape (C++ and Java do automatic overriding if a method has the same name, result and parameter types).

Now we will be able to do:

C#
rectangle.Draw()
line.Draw()

And even

C#
someUnknownShape.Draw();

If the compiler knows the real type at compile-time it is a possible optimization to invoke a virtual method as if it was not virtual.

This is the case of:

C#
Rectangle rectangle = new Rectangle(0, 0, 100, 100, 0xFF0000)
rectangle.Draw();

In this case, the compiler might be capable of knowing that the rectangle variable is really a Rectangle, not a sub-class of Rectangle, and will avoid the virtual call.

The result will be the same if the call is virtual or not, but virtual calls are slower than non-virtual calls, so doing such kind of optimization might be good. Of course, we can't do this kind of optimization if we have a DrawAll method that receives an array of shapes. We don't know if those shapes are Rectangles, Lines or others.

With the virtual call, that exists thanks to the base class or interfaces, things will work. It is not as dynamic as in JavaScript or other dynamic languages, but it is actually faster.

Low Level Details

I wanted to keep this article simple, but I believe it is easier to understand virtual methods (and why many languages don't make all methods virtual by default) by understanding the low-level details. Those are details the compiler do for us to make the code simpler.

As you probably know, everything the computer can execute must be loaded in memory and every location in memory can be represented by a number.

When we do a non-virtual call like:

C#
rectangle.Draw (... parameters here...)

The compiler will determine where the Rectangle.Draw method is related to the beginning of the file and, instead of keeping a call by the "Draw" name, it will do something like:

  1. Fill the parameters in processor registers and/or stack. The rectangle itself is a hidden first field;
  2. Call the address of the Rectangle.Draw method.

This means that if the Rectangle.Draw starts at the byte 500 from the beginning of the file, the method call will be CALL 500.

If 500 is the address of Rectangle.Draw, how will we be able to call line.Draw?

Well, if the Line type is known at compile time, the compiler can determine the address of Line.Draw (or DrawLine... if the call is not virtual this will really be the same) and do the appropriate call. So, supposing it is at address 700, it will be CALL 700.

If the type can't be determined at compile-time, then it should use an alternative call. This is where the virtual calls, like shape.Draw(), come into play as the compiler can't know if it should CALL 500, CALL 700 or something else. The same compiled code can use the address 500, 700 or another one depending on the object.

In most OOP languages the virtual methods are put inside a VTable (virtual table). A VTable is like an array of function pointers. Then, every object has a first "hidden field" that points to this VTable.

All Rectangles will point to the Rectangle VTable, all Lines will point to the Line VTable and, considering they inherit from Shape, the VTable will start with the Shape VTable content. The child classes are free to replace the items in their VTable, and by replacing those that come from the base type they actually "reimplement" it. This is how we can implement the Draw method on the sub-classes.

Of course, the compiler needs to change how the calls are done. So, when we call shape.Draw, what the compiler needs to do is something like this:

  1. Fill the parameters in registers or stack, as normal;
  2. Then, using the shape, reads the hidden field that references the VTable;
  3. With the VTable, it reads the function pointer array in the appropriate position for our call;
  4. Finally, it calls that address it just read.

If we try to put such code in C, it will be like this:

C
shape->vTable->Draw(shape, ... all other parameters ...);

And we should remember that this Draw is not a method, it is a function pointer.

Why do we need a base class? Why not consider all methods with the same signature (signature is the name, input parameter types and result type) as being naturally the implementation of the same method?

Because the VTable has a fixed size considering all the virtual methods in the actual type and its base types. If we really want to consider all compatible methods as being virtual independently of their types (and considering that other DLLs may be loaded later) we will need to use some different technique of indexation, which will surely be slower than a direct VTable lookup by index. This is what happens in dynamic languages, like JavaScript.

The base class is accepted but why aren’t all calls virtual?

For performance, to reduce the size of those VTables and because a class that is not meant to be sub-classed is probably not prepared for this. Most programmers do less validations on method results when they believe the method is not going to be reimplemented, so reimplementing it can cause serious bugs (many developers actually consider the fact that Java methods are virtual by default as a design flaw).

Can we do those virtual calls in C?

Sure. The example I gave in C will work but it will be our responsibility to create the VTable. Also, it can be very annoying for the users of the "class" to have to use the shape variable twice, one to read the VTable and the function pointer to call and then to pass it as the first parameter, as this is a needed parameter to the function pointer (and we can't simply store the shape in the VTable pointers because it is a single VTable for all objects of a given type).

Component Oriented Languages

For some time I saw many documents and web pages saying that C# is a Component Oriented Language but without an explanation of what is a Component Oriented Language. Such expression seems to be exactly the same as Object Oriented Language and many people really say it is the same thing.

The Wikipedia explanation makes things even worse as it appears that Component Oriented Languages give less features than Object Oriented Languages. So, why will someone be proud to say that a language "is not only Object Oriented, it's Component Oriented", if this gives less features?

Well, I searched about the topic for some time and I can say that there are two different things seen as important for a language to be considered Component Oriented.

The first item is, in my opinion, a precision of something that Object Oriented Languages should always have with good encapsulation but because of failed implementations (like in C++) people decided to create a new term instead of trying to say that C++ was not Object Oriented (I think C++ has a very strong reputation of being OOP, so "no one" discusses it). The second item is more related to how components created by those languages can be manipulated in a visual editor.

Modification of the internal details of a class should never cause breaking changes

The sole purpose of the private visibility is to hide implementation details from the clients of the class. So, I should ask: Why would we want to hide details from the clients?

In my opinion, for two main reasons:

  • To avoid users from putting inconsistent states in instances of our classes;
  • To be free to change the internal details without breaking existing clients.

But in C++, if we simply add new fields to a class we would cause client code that lives in a different library/executable to crash, as the new operator uses those "internal details" to calculate the size of the allocation, which may be invalid when the new version of the component is used.

So, a Component Oriented Language should allow components (that are still classes) that live in different libraries to be updated individually without causing problems to dependent compiled code.

Surely it is impossible to forbid developers from doing breaking changes to the components, but at least Encapsulation will work really fine. As long as we don't change the signature of public and protected members, the new version of a component will work. We are free to completely replace the private fields (making the component bigger or smaller in memory) and to add new methods and properties to our components and the client code, which is not going to be recompiled, will continue to work fine.

In this sense both Java and C# are Component Oriented Languages, C++ is not. C++ can of course create components but it is the responsibility of the component creator to provide the right Create/Destroy methods and the responsibility of the users to correctly use them instead of using the new and delete operators.

Discoverable compiled code

The second item that's important for a Component Oriented Language is to allow those components to be created and manipulated by a visual editor. The support for properties, events and custom information on those two is considered fundamental, as well as storing the information about the existing types, properties and events on the compiled code.

Both C# and Java include enough information on the compiled code to allow us to list the existing types and their members and to instantiate objects and manipulate them thanks to such information. There are visual editors for Java capable of using the get/set methods as properties but for some that's an work-around because Java doesn't have real properties. As C# has real support for properties and events and those can be decorated with [Attribute]s, C# is considered Component Oriented.

In my opinion the support for real properties and events shouldn't be taken into account to define what is a Component Oriented Language and is used to try to take Java out of the comparison. In fact, if I could simply redefine the terms freely, I will say that the capacity to change the private members without breaking client code should be part of OOP (it's the purpose of Encapsulation, after all) and a Component Oriented Language is one that is Object Oriented and also allows the compiled code to be analyzed to discover the existing types and members and to create and manipulate them after discovered, so they can easily be put into a visual editor.

By this personal definitions, C++ is not Object Oriented. It is almost there, but the problem with the new operator is enough to get it out of the Object Oriented Languages. In contrast, both C# and Java are Component Oriented Languages. C# can make things easier for the editor by having real properties and events but this doesn't mean Java is less Component Oriented.

What about C and C++?

We can always create work-arounds in C and C++. Nothing forbid developers from creating methods that return the definition of the existing classes and members, but keeping things in sync can be very problematic, as nothing in the language will forbid developers from saying that a class has a given method when it doesn't, or simply writing the wrong name in the definition. So, it is possible to create components in C and C++, but I don't think it's worth doing it (at least not by hand... it is always possible to create code generators that keep things in sync).

Conclusion

I don't have any real conclusion. I only hope this article is useful to anyone wanting to learn a little about Object Oriented Programming.

Do you have a question or don't agree with something? Ask me by using the forum. I will try to answer your question and, depending on the case, I will update the article to help others that may have the same question.

Version History

  • 31, March, 2014: Added the Component Oriented Languages topic;
  • 30, March, 2014: Added the Using Encapsulation and Using Polymorphism topics;
  • 29, March, 2014: Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
QuestionFrom your Article: "assembler can be an OOP language as we can create objects in assembler" Pin
Nirosh30-May-18 19:08
professionalNirosh30-May-18 19:08 
AnswerRe: From your Article: "assembler can be an OOP language as we can create objects in assembler" Pin
Paulo Zemek20-Jun-20 4:34
mvaPaulo Zemek20-Jun-20 4:34 
GeneralNice shot, suggest to add Schreiner 's ooc as further reading Pin
mathson201419-Jan-15 6:42
mathson201419-Jan-15 6:42 
GeneralRe: Nice shot, suggest to add Schreiner 's ooc as further reading Pin
Paulo Zemek19-Jan-15 7:47
mvaPaulo Zemek19-Jan-15 7:47 
QuestionBest OOP article ever seen Pin
SamUS.NET30-Dec-14 22:06
SamUS.NET30-Dec-14 22:06 
AnswerRe: Best OOP article ever seen Pin
Paulo Zemek30-Dec-14 22:12
mvaPaulo Zemek30-Dec-14 22:12 
QuestionGood but?? Pin
Nirosh3-Jun-14 21:28
professionalNirosh3-Jun-14 21:28 
AnswerRe: Good but?? Pin
Paulo Zemek4-Jun-14 2:24
mvaPaulo Zemek4-Jun-14 2:24 
GeneralRe: Good but?? Pin
Nirosh4-Jun-14 6:36
professionalNirosh4-Jun-14 6:36 
GeneralRe: Good but?? Pin
Paulo Zemek4-Jun-14 6:59
mvaPaulo Zemek4-Jun-14 6:59 
QuestionVery well written Pin
AmityGames7-Apr-14 19:27
AmityGames7-Apr-14 19:27 
AnswerRe: Very well written Pin
Paulo Zemek8-Apr-14 1:26
mvaPaulo Zemek8-Apr-14 1:26 
GeneralMy vote of 5 Pin
JOE Heart Under Blade7-Apr-14 16:03
JOE Heart Under Blade7-Apr-14 16:03 
GeneralRe: My vote of 5 Pin
Paulo Zemek7-Apr-14 16:06
mvaPaulo Zemek7-Apr-14 16:06 
QuestionMy vote of 5 Pin
Fergal19703-Apr-14 4:20
Fergal19703-Apr-14 4:20 
AnswerRe: My vote of 5 Pin
Paulo Zemek3-Apr-14 5:01
mvaPaulo Zemek3-Apr-14 5:01 
GeneralMy vote of 5 Pin
Jamal Alqabandi30-Mar-14 23:55
Jamal Alqabandi30-Mar-14 23:55 
GeneralRe: My vote of 5 Pin
Paulo Zemek31-Mar-14 1:00
mvaPaulo Zemek31-Mar-14 1:00 
QuestionNice one Pin
Florian Rappl29-Mar-14 22:06
professionalFlorian Rappl29-Mar-14 22:06 
AnswerRe: Nice one Pin
Paulo Zemek29-Mar-14 23:58
mvaPaulo Zemek29-Mar-14 23:58 
GeneralRe: Nice one Pin
Florian Rappl30-Mar-14 4:55
professionalFlorian Rappl30-Mar-14 4:55 
GeneralRe: Nice one Pin
Paulo Zemek30-Mar-14 5:28
mvaPaulo Zemek30-Mar-14 5:28 
GeneralRe: Nice one Pin
Paulo Zemek30-Mar-14 5:55
mvaPaulo Zemek30-Mar-14 5:55 
AnswerRe: Nice one Pin
irneb31-Mar-14 4:44
irneb31-Mar-14 4:44 
GeneralRe: Nice one Pin
Paulo Zemek31-Mar-14 5:00
mvaPaulo Zemek31-Mar-14 5:00 
I didn't talk about multiple inheritance on purpose.
See, you are talking about the dot, but C++ uses -> a lot and is still considered (by most, at least) to be an Object Oriented Language.
This is why I focused on two items: Polymorphism and Encapsulation. Nothing else.
How a class is declared, if we use dots or spaces to access the inner members etc is not important.

I also purposely avoided the "definition" that uses message passing, as a method call can be interpreted as a message being received... while some people go to extremes and says that message passing requires a real message infrastructure.

And that's why I said that, in my opinion, what matters is how easy the language makes the utilisation of Encapsulation and Polymorphism.

Actually, if I would advocate the term, Object Oriented Language must be a language oriented towards objects.
So, C is not object oriented because the language was not created with this purpose;
C++ is not object oriented because it added object support to C, so it is still very C oriented;
Java is almost object oriented. The primitives aren't objects, so we can argue that it is not completely oriented towards objects, but this is a problem only with the primitives. Everything else is object oriented;
C# is object oriented only when unsafe code is disabled (but it is disabled by default) because even an int value is an object. Yet, as it supports unsafe code some will argue that it allows us to avoid objects.
JavaScript is object oriented because everything is an object. It doesn't conform to the standard way of encapsulation (and it is even less complete than in other languages) but everything is an object and it doesn't allow unsafe invokes to the operating system.
But the fact that even a function is an object is a kind of "lying to ourselves". If we do a program that's works like a structured program some people will say: "Ah, but a function is an object, so it is object oriented".

So I will return to my idea: There are some basic ideas about what are objects and some of its "principles" and languages that make using those principles easy are object oriented.
In fact, I even though about talking about Component Oriented Languages. In my opinion this term only appeared because Object Oriented Languages had some bad implementations while still considered OO, so instead of requiring guaranties of object oriented languages, they created a new term. The fact that private member changes can still break client code in C++ doesn't make it less object oriented (even if this goes against the purpose of encapsulation) but makes it incapable of being Component Oriented.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.