Object-oriented programming, also referred to as OOP, is a programming paradigm that relies on the concept of classes and objects. Objects have state(s) (data in the form of fields and variables) and behaviour(s) (methods to operate on data).
Classes are blueprints for creating objects. Thus, objects are instances of a class.
The core idea of OOP is to divide a complex program into a collection of objects communicating with each other. One class does not need to know anything about the internal mechanics of another class. Classes only need to be able to communicate with each other. The instances of a class (objects) should not be able to change the state or behaviours of that class.
How are these rules achieved? By using the four pillars of OOP: abstraction, encapsulation, inheritance, and polymorphism. A brief note about data hiding: data hiding is the concept of hiding data and methods manipulating that data from the outside world. Data hiding is achieved by using abstraction or encapsulation.
The most important concept is abstraction. Why? It is paramount to reduce complexity while designing and building systems. “Designing and building things that work is at the heart of engineering. Engineers have to work together in teams to build complex systems, whether software, hardware, cars, planes, or bridges. This complexity is a major source of engineering cost and errors. With the wrong engineering approach, every team member can end up needing to understand every other team member's work, which means the total effort requires to build a system of size n is proportional to n2!" For more details, follow this link.
At its heart, abstraction in OOP is similar in meaning with a dictionary’s definition: the act of taking away or separating; withdrawal. Thus, abstraction in OOP refers to hiding unnecessary details from type consumers and giving only relevant data to reduce complexity. Abstraction focuses on what, not how, solving data hiding at the design level (modelling).
Classes are an abstraction of entities in real life: you design an abstraction of a bank account keeping only the relevant details to your application. Every bank account has an opening date but if your application does not need it, you do not add OpeningDate field in your BankAccount class.
Examples of abstraction: every button on your appliances. By pressing the volume button on a TV remote, we get a louder/quieter volume. How is this achieved by the TV it is hidden from us, we only need to know the exposed functions by the remote. In C#, there is System.Math class that exposes a lot of built-in methods. The actual implementation of Math.Min() or Math.Pow() is hidden from us.
In C#, to implement abstractions, we can use abstract classes or interfaces. As interfaces can only have abstract members (what) and no implementation details (how), they offer 100% abstraction. Whoever uses abstract methods or classes in their code, must supply an implementation for those methods/classes. The “how it is to be done” is not important, only the “what should be done”.
Encapsulation is another OOP concept that a) bundles together related functionality and b) gives access only to needful. A group of related properties and methods (state and behaviours) are treated as a single unit to restrict access from outside. Encapsulation solves data hiding at the implementation level. An example of encapsulation is, of course, a class.
To achieve encapsulation, it is considered a good practice to declare all fields private and use getter and setter methods for these private fields.
C# provides another option, properties, for getters and setters. These properties can be auto-implemented or not. With auto-implemented properties, no private fields are declared in the code. Instead, the compiler declares a private field and implements its getter and setter. Use non-auto-implemented properties when you need conditional checks while setting or accessing the fields.
Another way of achieving encapsulation is by denoting internal methods with the private keyword and designating methods intended for use by code outside the class with the public keyword.
Inheritance is the mechanism of creating a new class from an existing one (base class). The new class can extend the base class by inheriting all its non-private fields and methods.
Whenever we see an IS-A relationship between objects, we can use inheritance. Let's say shape is a base class. Square is a shape; circle is a shape; triangle is a shape; water is not a shape.
Inheritance allows code reusability (common code from the base class) and extensibility (new code from the derived class).
Polymorphism or “many shapes" in Greek, represents the concept of the same object displaying different behaviours. An example of polymorphism would be a vending machine with different items to sell. A customer can buy any of the items: chocolate, beverages, snacks.
Let's say we have a base class and some derived classes. Polymorphism allows us to use a collection or a list with a mix of all these derived classes. So, when a method expects an instance of the base class, at runtime the correct implementation of the method is evaluated. Because binding is done at runtime, performance can be somewhat worse.
Polymorphism is achieved in C# using virtual and override keywords. Polymorphism depends on inheritance as method overriding needs inheritance and there should be at least one derived class.
I hope this article helps to answer one of the most asked questions of programming interviews.