In the first semester we were introduced to OOP concepts like encapsulation, data hiding, modularity, inheritance and so on through Java and UML. (Java is my first programming language)
None of those are OOP concepts. They all exist outside of OO, independent of OO and many even were invented before OO.
So, if you think that that is what OO is all about, then your conclusion is right: you can do all of those in procedural languages, because they have nothing to do with OO.
For example, one of the seminal papers on Modularity is On the Criteria To Be Used in Decomposing Systems into Modules. There is no mention of OO in there. (It was written in 1972, by then OO was still an obscure niche, despite already being more than a decade old.)
While Data Abstraction is important in OO, it is more a consequence of the primary feature of OO (Messaging) than it is a defining feature. Also, it is very important to remember that there are different kinds of data abstraction. The two most common kinds of data abstraction in use today (if we ignore "no abstraction whatsoever", which is probably still used more than the other two combined), are Abstract Data Types and Objects. So, just by saying "Information Hiding", "Encapsulation", and "Data Abstraction", you have said nothing about OO, since OO is only one form of Data Abstraction, and the two are in fact fundamentally different:
- With Abstract Data Types, the mechanism for abstraction is the type system; it is the type system that hides the implementation. (The type system need not necessarily be static.) With Objects, the implementation is hidden behind a procedural interface, which doesn't require types. (For example, it can be implemented with closures, as is done in ECMAScript.)
- With Abstract Data Types, instances of different ADTs are encapsulated from each other, but instances of the same ADT can inspect and access each other's representation and private implementation. Objects are always encapsulated from everything. Only the object itself can inspect its own representation and access its own private implementation. No other object, not even other objects of the same type, other instances of the same class, other objects having the same prototype, clones of the object, or whatever can do that. None.
What this means, by the way, is that in Java, classes are not object-oriented. Two instances of the same class can access each other's representation and private implementation. Therefore, instances of classes are not objects, they are in fact ADT instances. Java
interfaces, however, do provide object-oriented data abstraction. So, in other words: only instances of interfaces are objects in Java, instances of classes are not.
Basically, for types, you can only use interfaces. This means parameter types of methods and constructors, return types of methods, types of instance fields, static fields, and local fields, the argument to an
instanceof operator or a cast operator, and type arguments for a generic type constructor must always be interfaces. A class may be used only directly after the
new operator, nowhere else.
For example, for modularity we can just divide the program into many small programs that perform well-defined tasks whose code is contained in separate files. These programs would interact with each other through their well-defined input and output. The files may be protected (encrypted?) to achieve encapsulation. For code re-use we can just call those files whenever they are needed in new programs. Doesn't this capture all what OOP is or am I missing something very obvious?
What you describe is OO.
That is indeed a good way to think about OO. In fact, that's pretty much exactly what the original inventors of OO had in mind. (Alan Kay went one step further: he envisioned lots of little computers sending messages to each other over the network.) What you call "program" is usually called an "object" and instead of "call" we usually say "send a message".
Object Orientation is all about Messaging (aka dynamic dispatch). The term "Object Oriented" was coined by Dr. Alan Kay, the principal designer of Smalltalk, and he defines it like this:
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.
Let's break that down:
- messaging ("virtual method dispatch", if you are not familiar with Smalltalk)
- state-process should be
- locally retained
- extreme late-binding of all things
Implementation-wise, messaging is a late-bound procedure call, and if procedure calls are late-bound, then you cannot know at design time what you are going to call, so you cannot make any assumptions about the concrete representation of state. So, really it is about messaging, late-binding is an implementation of messaging and encapsulation is a consequence of it.
He later on clarified that "The big idea is 'messaging'", and regrets having called it "object-oriented" instead of "message-oriented", because the term "object-oriented" puts the focus on the unimportant thing (objects) and distracts from what is really important (messaging):
Just a gentle reminder that I took some pains at the last OOPSLA to try to remind everyone that Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea.
The big idea is "messaging" -- that is what the kernal of Smalltalk/Squeak is all about (and it's something that was never quite completed in our Xerox PARC phase). The Japanese have a small word -- ma -- for "that which is in between" -- perhaps the nearest English equivalent is "interstitial". The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be. Think of the internet -- to live, it (a) has to allow many different kinds of ideas and realizations that are beyond any single standard and (b) to allow varying degrees of safe interoperability between these ideas.
(Of course, today, most people don't even focus on objects but on classes, which is even more wrong.)
Messaging is fundamental to OO, both as metaphor and as a mechanism.
If you send someone a message, you don't know what they do with it. The only thing you can observe, is their response. You don't know whether they processed the message themselves (i.e. if the object has a method), if they forwarded the message to someone else (delegation / proxying), if they even understood it. That's what encapsulation is all about, that's what OO is all about. You cannot even distinguish a proxy from the real thing, as long as it responds how you expect it to.
A more "modern" term for "messaging" is "dynamic method dispatch" or "virtual method call", but that loses the metaphor and focuses on the mechanism.
So, there are two ways to look at Alan Kay's definition: if you look at it standing on its own, you might observe that messaging is basically a late-bound procedure call and late-binding implies encapsulation, so we can conclude that #1 and #2 are actually redundant, and OO is all about late-binding.
However, he later clarified that the important thing is messaging, and so we can look at it from a different angle: messaging is late-bound. Now, if messaging were the only thing possible, then #3 would trivially be true: if there is only one thing, and that thing is late-bound, then all things are late-bound. And once again, encapsulation follows from messaging.
Similar points are also made in On Understanding Data Abstraction, Revisited by William R. Cook and also his Proposal for Simplified, Modern Definitions of "Object" and "Object Oriented":
Dynamic dispatch of operations is the essential characteristic of objects. It means that the operation to be invoked is a dynamic property of the object itself. Operations cannot be identified statically, and there is no way in general to [know] exactly what operation will executed in response to a given request, except by running it. This is exactly the same as with first-class functions, which are always dynamically dispatched.
In Smalltalk-72, there weren't even any objects! There were only message streams that got parsed, rewritten and rerouted. First came methods (standard ways to parse and reroute the message streams), later came objects (groupings of methods that share some private state). Inheritance came much later, and classes were only introduced as a way to support inheritance. Had Kay's research group already known about prototypes, they probably would have never introduced classes in the first place.
Benjamin Pierce in Types and Programming Languages argues that the defining feature of Object-Orientation is Open Recursion.
So: according to Alan Kay, OO is all about messaging. According to William Cook, OO is all about dynamic method dispatch (which is really the same thing). According to Benjamin Pierce, OO is all about Open Recursion, which basically means that self-references are dynamically resolved (or at least that's a way to think about), or, in other words, messaging.
As you can see, the person who coined the term "OO" has a rather metaphysical view on objects, Cook has a rather pragmatic view, and Pierce a very rigorous mathematical view. But the important thing is: the philosopher, the pragmatist and the theoretician all agree! Messaging is the one pillar of OO. Period.
Note that there is no mention of inheritance here! Inheritance is not essential for OO. In general, most OO languages have some way of implementation re-use but that doesn't necessarily have to be inheritance. It could also be some form of delegation, for example. In fact, The Treaty of Orlando discusses delegation as an alternative to inheritance and how different forms of delegation and inheritance lead to different design points within the design space of object-oiented languages. (Note that actually even in languages that support inheritance, like Java, people are actually taught to avoid it, again indicating that it is not necessary for OO.)