Any application requirement can be seen in two different views leading to different approaches towards extracting components for design and development. First approach looks at the requirement in terms of "how" each module should work. The requirement is split into different modules that take care of different parts of the functionality. These parts work on, and pass along data, neatly crafted into data structures, so that the functions can handle and update it correctly.
The second approach tries to be more refined about identifying modules in terms of "who" the module is. Once requirement is correctly split into modules appropriately identifying who they are; their functioning follows very naturally. The first is called the functional paradigm and the second is called the object oriented paradigm. Consider for example, a simple desktop application of a plain text editor. When we look at it from a functional perspective, the functionality can be roughly split into several parts:
A main function that invokes all the functions in the appropriate sequence, passing along the appropriate data. On the other hand, when seen from the object oriented perspective, the design looks like this:
There are two major points differences between the two. In the functional design, the central controller is really thick - it has to take care of a lot of aspects of the application, where as in the object oriented system, the central controller is minimal. It just needs to hand over to the modules - that take care of communicating with each other.
Notice the amount of modularity that is introduced by looking at the modules as "Who" instead of "How". The key is just identifying these components. The rest will follow. The user interface and File I/O are definitely independent of each other, whereas the different components of the UI are tied to each other. They cannot be separated. The skill in object oriented design is identifying such components and correlating them appropriately. Then the applications are bound to be more maintainable.
Even in functional programming, one can and should follow the discipline of separating and clubbing related functions - in different files of folders. But, such discipline is easier to break.
People often confuse object oriented design with object oriented languages. Object oriented languages only offer features that make it easy to code the object oriented design. That certainly does not mean that any code written in Java is object oriented and any code written in C is functional. Object Orientation is a way of thinking and a design paradigm - that makes the application modules more intuitive and maintainable.
Having said that, let's go ahead with trying to understand some of the basics concepts in Object Oriented Design.
The fundamental concept of object oriented programming is abstraction. Consider for example the human body. Billions of cells of various types interact with each other in various different ways, to create this amazing structure called human body. Do we ever realize this while talking to another person? What we see is a well-dressed body that walks, talks, sees and communicates with the world in its unique way, which although unique, is quite similar to any other human body. The external behavior rarely reminds us of what is going on within.
That is true abstraction. An object oriented module should export a well-defined interface that is useful to the external system. It hides within itself the intricate details of how it achieves that functionality. It does it by itself. The user of the module does not need to worry about how the given task is achieved. All that it should know is the exported methods and the way they should be invoked in order to get its job done.
A good object oriented design separates the application into different modules of classes - that export a set of methods for the external world and hide the actual functionality within. To get a good design, one must learn to look at the object as "Who" and once the module is well defined, then bother about the "How". Designers often tend to do things other way round and break the paradigm. The design should not start with what is easier to implement in the module followed by deciding what the module will do. We should first clearly break the application into modules and define what each module should do and then look for the best way of doing it. Only then can we achieve true abstraction - defining a simple external interface independent of the inner complexity.
As per the dictionary, to encapsulate something means to enclose something within. That is exactly what we require in order to achieve abstraction. In OO terms, Encapsulation is the mechanism of holding within a class, the data and code related to a given functionality. This is an important aspect of object oriented programming. That is the core reason why object oriented design gained its popularity. One can consider Abstraction as the requirement and encapsulation one of the best ways to achieve it.
In colloquial terms to inherit means to get everything from the source - naturally - without any particular "effort". In OO terms, inheritance is the mechanism whereby one class gets all the properties of another, without having to explicitly define them. In Java, we say the child class extends the parent class.
One can consider Inheritance as a special case of Encapsulation - where the child class contains the properties of the parent. But, in design, there is a fundamental difference between the two. Encapsulation is called "has a" relationship whereas Inheritance is called "Is a" relationship. For example, a dog Is A pet animal, where as a man Has A pet dog.
It is very important to understand this difference in order to efficiently design a system; because the basic strength of OO design is in observing and understanding the system As-Is, and identifying the relations between the objects therein.
Polymorphism is another important feature that OO languages offer, to enable good design and implementation. Polymorphism essentially allows a line of code to behave differently based on the context. There are two basic types of polymorphism - static and dynamic. Static polymorphism is one that allows the compiler to decide which function should be invoked in the particular line of code. But, dynamic polymorphism cannot be defined till the code actually executes.
Static polymorphism is implemented using function overloading - where a function of the same name invokes two different code segments depending on the parameters passed. The types of the parameters passed are known to the compiler and hence it can take care of statically assigning these handles. Hence it is called compiler polymorphism or static polymorphism.
Dynamic polymorphism on the other hand is based on function overriding - where a function of the parent class is overridden by a subclass. In such a case, when the function is invoked for a parent class reference, the compiler is not sure if the actual object that it will find at runtime would be of parent class or the child class. In such a case, it cannot decide which function should be invoked there. This is decided only at run time, by the JVM.
Abstraction, Encapsulation, Inheritance and Polymorphism are the basic tenats of OO design and coding. They form the basis. Hence it is very important to understand these concepts very well before we proceed.