Lesson 1 - Introduction to object-oriented programming in C++
Welcome to the first lesson of the object-oriented C++ programming course. We finished the C++ basic constructs course last time with the article about Structures in the C++ language. In this course, you will learn to program in an object-oriented way and will also develop an object-oriented way of thinking. It's a bit different than anything we've done until now. For starters, we will no longer treat programs like several lines of code that are executed one by one.
The invention of object-oriented programming, OOP hereinafter, was no coincidence, but the result of a development which led to its creation. It's a modern software development methodology supported by most programming languages. A common mistake is that people think that OOP should only be used for certain kinds of programs and that for other cases it would be needlessly complicated; however, the opposite has proven to be true. OOP is a philosophy. It's a new way of looking at a program and the communication between its parts. We should always use it whenever we create a simple utility or a complex database system. The OOP is not just a technology or a recommended program structure, it's mainly a new way of thinking. A new perspective from which we could analyze problems and a new era in software development.
As first, we'll look quickly into the history of how people programmed before OOP and what specific problems OOP resolves. It's important for us to fully understand why the OOP had to be created.
The evolution of methodologies
There is a big difference between programming nowadays and programming 40 years ago. The first computers didn't have great performance and their software was quite simple. The evolution of hardware is so fast that the number of transistors in microprocessors doubles every year (Moore's Law). Unfortunately, people aren't able to evolve as quickly as hardware does. Faster computers require more and more sophisticated and complex software (people want more and more from their computers). So much that at one point, it was found out that about 90% of all software doesn't meet deadlines, requires additional costs or isn't finished at all. Developers started looking for new ways to write programs. Several new approaches took turns. More precisely, paradigms (ways of thinking). Listed as follows:
1. Machine code
The program was just a set of instructions where we weren't able to name variables or enter mathematical expressions. The source code was obviously specific for the then current hardware (processor). This paradigm was replaced soon after it was established.
2. The unstructured paradigm
The unstructured approach is similar to the assembly languages, it's a set of instructions which are executed line by line. The source code wasn't dependent on the hardware and was human readable. This approach enabled the creation of more complex programs for a while. There were still many pitfalls: the only way to repeat something or to branch off code was the GOTO statement. GOTO allowed to "jump" to different locations within the program. Locations were previously specified by a line number in the source code, which is obviously impractical. When we insert a new line of code somewhere, the numbers no longer match and the code is broken. Later, it was made possible to define what they then called "labels". This approach served as a way of simulating loops. This method of writing programs is of course very confusing and soon failed to be sufficient for developing complex programs.
Consider that the huge adoption of personal computers over the past few decades caused the growth of software demand and, naturally, a demand of more people who make programs (programmers). Certainly, there are people who can write bulletproof programs even in ASM or other low-level languages, but how many are there? How much does this superhuman job cost? It's necessary to write programs in a way that even less experienced programmers could write high-quality programs and not need to go through 5 years of experience to code a simple utility app.
3. The structured programming
Structured programming is the first paradigm that lasted for a longer time and was quite sufficient for the development of new programs. They would mostly program using loops and branching. Conceptually, we are in the structured programming era based on what we have learned from the first course.
The program would be decomposed into functions (methods). In structured programming, we meet a functional decomposition principle. A problem is decomposed into several subproblems and each subproblem is then solved with some parametrized function. The disadvantage to it is that a function can only do one thing and when we want a different behavior, we have to write a new one. There is no way to reuse old code and modify it. We need to write it again and again - this creates unnecessary, potentially costly, errors. This disadvantage can be partially worked around by using parametrized functions or using global variables. However, such universal functions usually require a lot of parameters to pass and are hard to use and maintain.
With global data, there's another pitfall. Functions can access the data of other functions. This is the beginning of the end, we can't guarantee that global data isn't being overwritten somewhere between functions. It leads to uncontrollable problems. The entire program will consist of unencapsulated code blocks and can hardly be maintained. Any modification increases the complexity of the program, and then the program will necessarily come to a situation where the cost of adding new features will overbalance the value added by these features. Languages using this approach are, for example, the C language and Pascal.
Between the structured programming and the object-oriented programming, there was one more intermediate approach called modular programming. It involved the encapsulation of specific functionality into modules. Regardless, there was no way to modify and reuse already written code.
As I mentioned at the beginning of the article, it's sometimes said that simple programs mustn't be written in an object-oriented way, but structural, which isn't true. If we program in a structural fashion, we'll end up making a blob that will be almost unreadable by most people. Then again, it would come to a point where the program wouldn't even be upgradable and we'd either have to throw it away or rewrite it using the OOP.
The non-object-oriented methods of writing code are called "spaghetti code" because of their lack of clarity (everything is tangled together like spaghetti).
The object-oriented approach
OOP is a philosophy and a way of thinking, designing and implementing solutions that focuses on reusability. This approach is inspired by the industrial revolution - the invention of basic components. For example, when we build our house, we don't burn our own bricks and forge nails, we order them.
Making a "component program" is smarter and cheaper. Components don't fail, they're tested and maintained. If there's a problem, it is most likely in the code you have written in one specific location. We're motivated to write clear code since it can be used by others or by ourselves in other projects. Let's face it, humans are lazy by nature and if we thought that our code wouldn't ever be reused, we simply wouldn't write it good enough ).
Of course, we'll use the knowledge we have gained until now. The main difference is that now, our code will be structured differently into multiple communicating objects.
How the OOP works
It tries to simulate reality as we're used to see it. We can say that we abandon the idea how the program is seen by the computer (machine) and write it from the programmer's (human's) point of view. As we had replaced the assembler with human-readable mathematical notations, now we're going even further and replace those, too. OOP is, therefore, a certain level of abstraction above the program. This has significant advantages because it's more natural and readable for us.
The basic component is object which corresponds with some object from the real world, e.g. a human object or a database object).
In older languages, methods didn't belong to objects but were loosely placed
in modules (units). We could call them by typing length(text)
instead of text.length()
. The disadvantage to that was, of course,
that the length()
method didn't belong to anything. There was no
way to list what string could do and the code was messy. Additionally, we
couldn't have two methods with the same name. In OOP we can have both
user.remove()
and article.remove()
. It's very clear
and simple. In a structured program we'd have to write:
remove_user(user)
and remove_article(article)
. We'd
have to create thousands of silly, unnecessary methods. If you're thinking, hey,
isn't that what the PHP language does? You are absolutely right. PHP is terrible
when it comes to things like this, and for that same reason, its design is
considered old. It became fully object-oriented later, but its foundations will
probably never change. Although C++ is an older language as well, it's been
created more carefully and offers us build-in classes.
In this article, we're going to explain the basics of how to create objects and how to encapsulate their internal logic. Other OOP features, mainly inheritance, will be explained in the following lessons, to not overload your brain today
Class
We have already encountered the term "class". We understood it as a set of commands. A class, however, allows us to do so much more. A class is a pattern that we use to create objects. It defines their properties and abilities.
An object created according to a class is called an
instance. Instances have the same interface as
the class according to which they were created, but they mutually differ in
their internal data (fields). For example, let's consider a Human
class and creating the carl
and jack
instances from
it. Both instances will have the same fields as the class (e.g.
name
and age
) and methods (goToWork()
and
greet()
), but the values in them will be different. For example,
the first instance will have the value "Carl"
in the name field and
22
in age
, the second will have the values
Jack
and 45
.
The communication between objects is performed by messaging which makes the
syntax clear. The message usually looks like this:
recipient.methodName(parameters)
. For example,
carl.greet(neighbor)
could cause the carl
instance to
greet the neighbor
instance.
OOP is based upon three core concepts:
- encapsulation
- inheritance
- polymorphism
If any of them doesn't ring any bell for you, don't worry, we're going to explain everything in detail in the following lessons. The main thing you should leave with today is that a class is just a recipe on the basis of which objects are created (called instances in programming). Next time, First OOP application in C++ - Hello object world, we'll be looking at class creation.