I am sorry about next three paragraphs. Because, I wrote too much. If you don't have time, you can skip them directly 🙂
The four principles of OOP are entry point to build a well designed OOP applications. You definetely need to know whether you are a junior or senior developer to write an effective code. Before telling about them, I just want to tell why you need to know them. Probably, you already use some of them even without knowing principles. But, when you know them, you will have a higher perspective and more confidence related with complex codebase(Projects). Because, writing a class is not difficult at the beginnig but when you have more complex codebase, it will be difficult to maintain development if you don't apply some patterns and principles. Furthermore, it is a common language between software engineers. So, easy to communicate and easy to design complex applications. They are also not dependent to any programming language like Java, C#, Python etc. If you know and understand what they mean, then you can definetely benefit from them to build an application. By the way, some developers named as different naming convention like basic principles of OOP, main concepts of OOP, basics of OOP, basic concepts of OOP, etc 🙂 But all of them are mentioning about same things Abstraction, Encapsulation, Inheritance and Polymorphism.
Novadays, developers have to be very pragmatist because of the market. They need to learn and use new stuffs very quickly. For example, I read every time about new frameworks. Although I don't develop any project with new framework, I spend my time to become ready on the software market. So, it is difficult to work and think on some basic concepts due to lock of time. But, I realized that I spend too much effort for every new framework or library to understand. When I anlyze different open-source projects for weeks, I had my way of exploring the projects in a time. This means that I want to follow same way every time unconsciously. It is very very funny 🙂 because I tried to create basic principles and patterns in my way unnecesarily. I every time try to find some key points. But, there is more funny side as well. Developers of open source projects were already applying basic principles. That is why, many developers could work together. In this case, I have learned many principles and patterns unconsciously. Finally, I learned the existence of basic concepts or principles of OOP :))))
Unfortunately I was not only one person who makes same mistakes in the market. Developers who have different experience still don't know or care for significance of OOP principles. This doesn't mean that they are not good developers. Many of them were developing complex projects with well known frameworks like Spring Boot, Express Framework, etc. But, they just copy and paste codes from tutorials generally. Big frameworks are making development easer and safer. So, we don't need to think for low level details for our applications and just need to think about business logic. So, it is decreasing development cost and increasing efficiency for the organizations(companies). But, in this case, developers are becoming lazy in a time and don't want to spend time to learn or even remember about basic principles of OOP and some common software patterns. Anyway, I decided to write this post for myself. I hope that some developers will benefit in the future as well 🙂
There are four basic principles of OOP as below;
I read many different posts about them so I will write different versions of definitions that I took note and you can decide which one is more clear to you 🙂 They are not my sentences. I don't want to create a new one. I just want to show the way of my thinking.
Terms used in OOP
- Class is a blueprint or template or set of instructions to build a specific type of object. Every object is built from a class. Each class should be designed and programmed to accomplish one, and only one, thing (Single Responsibility Principle – SRP).
Class is a template for objects, and an object is an instance of class.
Class provides better code reuse. Classes are the fundamental building blocks of object-oriented programming.
- Object is an instance of a class.
- Instance is a specific object built from a specific class.
- Sub Class/Child Class is a class which inherits from another class. It is also called a derived class, extended class, sub class or child class.
- Super Class/Parent Class being inherited from another class. It is also called a base class, super class or a parent class.
- Reusability is a mechanism which facilitates you to reuse the fields and methods of the existing class when you create a new class. You can use the same fields and methods already defined in the previous class.
- Reference is a variable or property that refers to an instance of a class.
- Instance variable is a variable that is associated with a specific instance of a class.
- Class variables also known as static variables are declared with the static keyword in a class, but outside a method, constructor or a block. There would only be one copy of each class variable per class, regardless of how many objects are created from it. These variables are shared among all instances of the class, and changing this variable will affect all instances of the class.
- Abstraction aims to hide complexity from the user and show them only the relevant information. For example, you can hide internal implementation details by using Abstract classes and Interfaces. This means that when you need to write a class, you need to define the method signatures (name and parameter list) and let each class implement them in their own way.
- Abstraction is used to manage complexity.
- Abstraction means hiding unessential details from the user and providing only essential information.
- Abstraction hides internal implementation details. For example, you need to know how to use coffee machine. You need to provide water and coffee beans, switch it on, etc. But, you don't need to know how the coffee machine is working internally. You don't need to know the ideal temperature of the water or amount of coffee. Similar to the coffee machine in your kitchen, you just need to know which methods of the object are available to call and which input parameters are needed to trigger a specific operation. But, you don't need to understand how this method is implemented and which kinds of actions it has to perform. Coffe machine in kitchen was presenting an interface to the user. OOP language, like Java or C# can achieve same abstraction with Abstract class or Interface. Think about interfaces in C# or Java, we just think and define about methods and parameters which will be public but we don't think business logic for those methods. So that, different classes can implement this abstraction (interface or abstract class) in their own way. Every implementation of an abstraction can be thought as a behaviour. So, if you write your implementation code according to abstraction (interface or abstract method), you will have flexibility to change the implementation of abstract behaviour.
- Abstraction is a process of hiding the implementation details from the user. Only functionality will be provided to the user.
- Abstraction shows only essential things to the user and hides the implementation details. So that, abstraction lets you focus on what the object does instead of how it does.
- Abstraction reduces the complexity of viewing the things.
- Abstraction is used to hide unwanted data and show only required properties and methods.
- Abstraction exposes necessary methods and properties without internal implementation details.
- Abstraction's main goal is to handle complexity by hiding unnecessary details from the user. That enables the user to implement more complex logic on top of the provided abstraction without understanding or even thinking about all the hidden complexity.
- Abstraction hides the internal implementation details. We just need to know which methods of the object are available to call and which input parameters are needed to trigger a specific operation. But you don't need to understand how this method is implemented and which kinds of actions it has to perform to create the expected result.
- Abstraction reduces complexity and increase efficiency. It omits unwanted details.
- Abstraction helps to reduce programming complexity and effort.
- In Java and C#, Abstraction is accomplished using Abstract classes and Interfaces.
- Abstraction helps the user to avoid writing the low level code.
- We can change internal implementation of a class independently without affecting the user.
- Abstraction means displaying only essential information and hiding the implementation details.
- Abstraction is to hide implementation using abstract classes and interfaces.
- Abstraction means we need to display what is necessary and compulsary and need to hide the unnecessary things to the outside world.
- Abstraction lets you focus on what the object does instead of how it does it.
- Abstraction provides you a generalized view of your classes by providing relevant information.
- Abstraction is a model of type(Class).
- Abstraction reduces the complexity of viewing the things.
🙂 Most of the above definitions are very similar to each other. You can prefer any of them. Anyway, everybody is saying same things shortly. According to me, before writing a class, as a developer, we always think about class methods. But not private methods or any internal business logic or implementation. We generally think about public methods and properties which class needs to expose for other classes. Hey look at here. We are still in design level and we are still thinking about methods and properties in our mind high level or as a concept. Actually, before starting development we are thinking what a class will do, we are not thinking how that class perform actions. So, we already apply abstraction concept or principle unconsciously. Abstraction is saying write your model of class in interface or abstract class in OOP, so that, we can write different implementation classes of this abstract class or interface.
We benefit from Abstraction in our applications from design level to the all life time. Abstraction helps us in design time by showing public methods. Then other classes in application use this abstract class or interface in their inner business logic. If we have a different implementation of abstract class or interface, classes which use this interface or abstract class will not be affected. Because, they used abstract class or interface. So, we can inject new implementation of the abstraction. We need Abstraction in design level and implementation level very much in order to have a proper development life cycle.
I created Employee example with different versions such as Abstraction with Abstract class or Abstraction with Interface. But, both ways give same result. If you have some base methods in your case, then you prefer abstract class. Otherwise, prefer interface.
Abstraction with Abstract Class
Notice that Employee is an Abstract Class and calculatePayment is an abstract method in the following sample code. So, what should we understand from this development as a developer? Employee is defined as an Abstract Class. Because, we try to create a model of concrete(actual) class of Employees. We can achieve this with Abstract class or Interface in C# and Java. Concrete class is an actual class which has business logic. So, we can create different concrete classes from this abstract class. But, why do we need to create a model of concrete class instead of creating concrete classes directly? Well, I don't want to give strange examples like coffee machine or remote control in real life. To explain briefly from OOP perspective, when we need to call any method of a concrete class in our business logic, we shouldn't know about that concrete class. Because we don't want our business logic or unit test to be affected due to future possible new implementation of any concrete class.
Let's repeate steps one more time, we need to create two concrete classes named ContractorEmployee and PermanentEmployee. Both classes will have same method named calculatePayment. There can be similar concrete classes in future as well. So, Don't program to implementation program to interface or abstract class. Abstraction should be a habit for any developer. We should know and understand that this class will be used in somewhere in project and maybe again later there can be a different implementation of this class and we don't want to find all the implementation in the project and replace again and again. If we use abstract class or interface in project wherever it is needed, then we don't need to be afraid about any new implementation. Because Abstract class and Interface are just a model. We can not create an instance from an interface. Implementation or any interface can be injected or applied in runtime.
I just used properties instead of get and set methods in Employee class.
Abstraction with Interface
I have used both Interface and Abstract class in the following code. Because, there is a common method named getFullName() for all implementation of IEmployee interface. If I didn't create Abstract class and implement directly to concrete classes then I need to write same business logic in all concrete classes. It is a repetation of code. So, I created one more abstraction layer and put getFullName() business logic in there.
Notice that I didn't use Employee abstract class in main.cs – I used IEmployee interface. I am comfortable from now on. Because, ContractorEmployee or PermanentEmployee can directly implement IEmployee or implement different abstract class. I have mentioned preceding paragraphs about business logic or unit test stability. So, business logic of main.cs will not be affected. I still use ContractorEmployee and PermanentEmployee concrete classes in main.cs In order to overcome for this issue, we could use an IoC Container. But, it is an other topic. I just want to focus on Interface and Abstract class in this post.
I created other example in which Interface is used only. I need to return hello message from a method. For example, sayHello() method can return hello message in Turkish and English. I need two different implementation to return two different hello message. Notice that I say two different implementation. This means that there can be more implementation in future as well. So, we need to have a model or in other words an abstraction. That is why, I created IGreeting interface with sayHello() method. I think that we are good now. Becuase, we know that we need to program everytime interfaces in our application not implementations. There can be many implementation of IGreeting interface but we will not have any problem because of concrete class changes.
Let's improve previous example code more as shown in the following new version of code 🙂 I moved business logic of Console.WriteLine and File.WriteAllText method calls to the seperate methods. I just try to use our abstraction (IGreeting) in a business logic or method. So, we don't need to update business logic of WriteMessageToConsole() and WriteMessageToFile() methods in the future. Because, we inject IGreeting interface. If there is a new implementation of this interface in the future then it will be injected easily without affecting business logic of those methods.
I put one more last code in the below to demonstrate abstraction clearly. Notice that I have create IGreetingWriter interface and writeText method to make abstraction. So, IGreetingWriter interface is implemented by two different classes named GreetingToConsoleWriter and GreetingToFileWriter. We don't need to worry about business logic of writeText() method of GreetingToConsoleWriter or GreetingToFileWriter classes anymore for the future changes. Our code is dependent to IGreetingWriter interface not concrete classes.
Encapsulation section will be shorter than Abstraction section. Abstraction is core concept of OOP according to me. I found some description about Encapsulation on the internet. They explain very well. The following descriptions are again not my sentences. I found them from different sources.
- Encapsulation is all about wrapping variables and methods in one single unit. Encapsulation is also known as data hiding. Because, when you design your class you may make your variables hidden from other classes and provide methods to manipulate the data instead. Your class should be designed as a black-box. You have access to several methods from outside (classes) and a return type for each of those methods.
- Encapsulation is defined as the wrapping up of data under a single unit. It is a protective shield that prevents the data from being accessed by the code outside this shield.
- Technically in encapsulation, the variables or data of a class is hidden from any other class and can be accessed only through any member function of own class in which they are declared.
- As in encapsulation, the data in a class is hidden from other classes, so it is also known as data-hiding.
- Encapsulation can be achieved by: Declaring all the variables in the class as private and writing public methods in the class to set and get the values of variables.
If you check sample codes in Abstraction section, you already see that I implemented abstraction and encapsulation together several times. But, this sample doesn't contain any interface or abstract class. it only emphasizes the encapsulation concept in OOP. As you can see in the following sample code, there is a Person class which wraps variables and methods in a single unit. Private variables and private methods are hidden from the other class. You have access to several public methods and return types from other classes. Encapsulation is a protective shield (you can think shield as a class) that prevents the private variables and methods from being accessed by the code outside this shield. Notice that I put some validations in both setFirstName() and setLastName() methods to force other classes for some rules. If method parameter is null or empty, then method throws exception so that, variable can not be set any invalid value by the other classes. Developers should wrap variables and methods in a class and make only essential methods or members as public, rest of the variables and methods should be private in order to protect any undesirable conflicts.
- Polymorphism allows you to invoke derived class methods through a base class reference during run-time. This is handy when you need to assign a group of objects to an array and then invoke each of their methods.
- Polymorphism is an object-oriented programming concept that refers to the ability of a variable, function or object to take on multiple forms.
- Polymorphism is the ability of an object to take on many forms. The most common use of polymorphism in OOP occurs when a parent class reference is used to refer to a child class object. Any Java object that can pass more than one IS-A test is considered to be polymorphic. In Java, all Java objects are polymorphic since any object will pass the IS-A test for their own type and for the class Object.
- Polymorphism allows us to perform a single action in different ways.
- Polymorphism means "many forms", and it occurs when we have many classes that are related to each other by inheritance. Inheritance lets us inherit attributes and methods from another class. Polymorphism uses those methods to perform different tasks. This allows us to perform a single action in different ways.
- Polymorphism is when you can treat an object as a generic version of something, but when you access it, the code determines which exact type it is and calls the associated code.
- Polymorphism allows you define one interface and have multiple implementations.
- Inheritance lets us inherit attributes and methods from another class. Polymorphism uses those methods to perform different tasks. This allows us to perform a single action in different ways.
- Subclasses of a class can define their own unique behaviors and yet share some of the same functionality of the parent class.
There are two types of polymorphism.
- Dynamic Polymorphism (Method Overriding)
- Static Polymorphism (Method Overloading)
It is called as Method Overloading, Compile-Time Polymorphism, Compile-Time Binding, Static Binding, Early Binding.
Notice that I have add() methods with different parameters two times in Calculator class. If we want to use any of those, we can decide compile-time. This is called Static Polymorphism.
It is called as Method Overriding, Run-Time Polymorphism, Run-Time Binding, Dynamic Binding, Late Binding
Notice that I have a virtual method named as sound() in Animal class and override in Cat and Dog subclasses. So, compiler will decide which method will be run in run-time. This is Dynamic Polymorphism.
- Inheritance can be defined as the process where one class acquires the properties (methods and fields) of another.
- The process by which one class acquires the properties(data members) and functionalities(methods) of another class is called Inheritance. The aim of inheritance is to provide the reusability of code so that a class has to write only the unique features and rest of the common properties and functionalities can be extended from the another class.
Notice that in the following example, I have Calculator class (Parent Class, Super Class or Base Class) and ScientificCalculator class (Child Class, Sub Class or Derived Class). ScientificCalculator class is derived from Calculator class so that ScientificCalculator will have two add() methods due to the Inheritance and mod() method. As you can see, we could reuse existing methods via Inheritance. Inheritance provides reusability.