As the name suggests, Object-Oriented Programming or OOPs refers to languages that use objects in programming. Object-oriented programming aims to implement real-world entities like inheritance, abstraction, polymorphism, and encapsulation in programming. The main aim of OOP is to bind together the data and the functions that operate on them so that no other part of the code can access this data except that function. In this article, we will understand some best practices of OOP’s.
The SOLID acronym is regarded as the best object-oriented programming philosophy. Let’s understand what this actually means along with some other important principles.
- Single Responsibility Principle: According to this principle, a class should have only a single responsibility or a single job or a single purpose. We should strictly avoid using generalized classes where the entire implementation is given in the same class. It also states that the responsibility should be entirely encapsulated by the class, module, or function.
- Open/Closed Principle: According to this principle, the software entities like classes, modules, functions, etc. should be open for extension and the classes should be closed for modification. This means that we should be able to extend a class behavior, without modifying it.
- Liskov’s Substitution Principle: According to this principle, the Derived or child classes must be substitutable for their base or parent classes. This principle ensures that any class that is the child of a parent class should be usable in place of its parent without any unexpected behavior.
- Interface Segregation Principle: This is the first principle that applies to an interfaces. It is similar to the single responsibility principle. It states that we should not force any client to implement an interface that is irrelevant to them. The main goal of this concept is to focus on avoiding fat interface and give preference to many small client-specific interfaces.
- Dependency Inversion Principle: According to this principle, high-level modules/classes should not depend on low-level modules/classes but rather, they should depend upon abstractions. We also need to ensure that the abstraction should not depend upon details but the details should depend upon abstractions.
Apart from the above principles, some other important practices that need to be practiced in object-oriented programming are:
1. Meaningful Names: The first practice that needs to be followed in the OOP’s concept is to use meaningful names. And also, all the methods must follow the camel case naming convention. We should always make the design in such a way that one class is responsible only for one particular task. If a class exists in the project which performs more than one task, it should further be divided in such a way that it holds only one responsibility. However, when we use object-oriented design principles, the responsibilities are predefined. For example,
- In builder pattern, the class creates objects. So, we use the name as the builder class.
- If a class acts as a mediator between two functionalities, then it is named as the mediator. Like CatMediator, DogMediator, etc.
- If we use a consumer type of design, then we can use the Adapter suffix to explain the responsibility of the class.
2. Fewer Arguments: We always need to write methods in such a way that the number of arguments is as minimal as possible. We can always use the values from other objects in the same class instead of asking the user the same input multiple times. And also, methods with too many arguments are difficult to read.
3. Avoid global and non-deterministic behavior : Whenever we use the OOP concept, we always need to ensure that the global behavior of the variables and objects are minimized. This can be visualized with an example of creating an animal cheetah. The color of the animal doesn’t change after its creation. So, we need to ensure that the attribute is not global and is unreachable to make sure data clashes don’t occur. Therefore, the use of global variables or objects needs to be avoided. We can use the concept of encapsulation on the data members to solve this issue.
4. Avoid static methods: Adding on to the above reason, the static methods must be avoided as much as possible because they act almost in a similar way as a global variable. And also, another important reason to avoid is that they create a secret dependency with the class where it is created and the dependency is not observed and revealed until the entire structure of the class is changed. This makes the maintainability a lot more difficult. And also, a static method cannot be tested in isolation.
5. Avoid using constructors: The constructors must strictly be avoided because they make the program usability difficult from a client’s perspective. In order to use a program that is invoked by a constructor, the client needs to remember the order of the parameters and the object initialization isn’t possible if the correct order is forgotten. In cases where the constructor demands multiple parameters, the usability of the program becomes a lot more difficult. One alternative solution is the builder pattern.
6. Reducing Conditional statements: The usage of conditional statements must be reduced as much as possible. Using too many conditional statements in the program increases the complexity as well as the code cannot be reused. Instead, we can make use of interfaces and abstract classes and implement the conditional logic in different methods which can be reused and also, the single responsibility of the methods and classes is maintained. Wherever we need to reuse the same conditioning, we simply call the method where it is implemented instead of writing the code again.
To conclude, the practices used in different programs and Softwares might be different and primarily incline towards the final outcome, but the above-mentioned practices are some of them which are universally followed and it makes the program more efficient, readable, reusable and easy to maintain.