Monday, January 8, 2024

OOPS Design Principles

DRY (Don’t Repeat Yourself)

One of the most important OOPs Design Principles is DRY, as the name suggests DRY (don’t repeat yourself) means don’t write duplicate code, instead use Abstraction to abstract common things in one place. If you are using JDK 8 or later versions, you can implement the method in interfaces as well. If you have the same block of code in more than two places, consider making it a separate method. Even if you use a hard-coded value more than once, make them public final constant.

Composition Over Inheritance (COI)

COI is an acronym for Composition Over Inheritance. As the name implies, this principle emphasizes using Composition instead of Inheritance to achieve code reusability. Inheritance allows a subclass to inherit its superclass’s properties and behavior, but this approach can lead to a rigid class hierarchy that is difficult to modify and maintain. In contrast, Composition enables greater flexibility and modularity in class design by constructing objects from other objects and combining their behaviors. Additionally, the fact that Java doesn’t support multiple inheritances can be another reason to favor Composition over Inheritance.

Composition allows changing the behavior of a class at run-time by setting property during run-time, and by using Interfaces to compose a class, we use polymorphism, which provides flexibility to replace with better implementation at any time.

Difference between Composition and Inheritance

Now let’s understand the difference between Inheritance and Composition in a little bit more detail.

Static vs Dynamic

The first difference between Inheritance and Composition comes from a flexibility point of view. When we use Inheritance, we have to define which class you are extending in code. It cannot be changed at runtime, but with Composition you just define a Type which you want to use, which can hold it’s different implementation. In this sense, Composition is much more flexible than Inheritance.

Limited code reuse with Inheritance

As aforementioned, with Inheritance you can only extend one class, which means you code can only reuse just one class, not more than one. If you want to leverage functionalities from multiple classes, you must use Composition. For example, if your code needs authentication functionality, you can use an Authenticator, for authorization you can use an Authorizer etc.  But with Inheritance you just stuck with only class, why? Because Java doesn’t support multiple Inheritance. This difference between Inheritance vs Composition actually highlights a severe limitation of later reusability.

Unit Testing

This is in my opinion, the most important difference between Inheritance and Composition in OOP probably is the deciding factor whether to use Composition or Inheritance. When you design classes using Composition, they are easier to test because you can supply a mock implementation of the classes you are using. But when you design your class using Inheritance, you must need a parent class in order to test it’s child class. There is no way you can provide a mock implementation of the parent class.

Final Classes

This difference between them also highlights the other limitation of Inheritance. Composition allows code reuse even from final classes, which is not possible using Inheritance because you cannot extend final class in Java, which is necessary for Inheritance to reuse code.

Encapsulation

The last difference between Composition and Inheritance in Java in this list comes from Encapsulation and robustness point of view. Though both Inheritance and Composition allow code reuse, Inheritance breaks encapsulation because in case of Inheritance, subclass is dependent upon super class behavior. If parent classes change its behavior, then child class will also get affected. If classes are not properly documented and child class has not used the super class in a way it should be used, any change in super class can break functionality in the subclass.

The Composition provides a better way to reuse code and same time protect the class you are reusing from any of its clients, but Inheritance doesn’t offer that guarantee. However, sometimes Inheritance becomes necessary, mainly when you are creating class from the same family.

Programming for Interface not for Implementation

This OOPs Design Principles say that Always program for the interface and not for implementation; this will lead to flexible code that can work with any new implementation of the interface. But hold on for a min and go through below lines!

An interface might be a language keyword and even an interface might also be a design principle. Don’t confuse both! There are two rules to think of:

  • Use interfaces (the language keyword) if you have multiple concrete implementations.
  • Use interfaces (the design principle) to decouple your own system from external system. It refers to loose coupling between modules or systems.

Minimize Coupling

Coupling between modules/components is their degree of mutual interdependence; lower coupling is better. In other words, coupling is the probability that code unit “B” will “break” after an unknown change to code unit “A”.

Coupling refers to the degree of direct knowledge that one element has of another. In other words, how often do changes in class A force related changes in class B.

What is Tight Coupling?

In general, Tight coupling means the two classes often change together. In other words, if A knows more than it should about the way in which B was implemented, then A and B are tightly coupled. For example, if you want to change the skin, you would also have to change the design of your body as well because the two are joined together, they are tightly coupled. The best example of tight coupling is RMI (Remote Method Invocation).

What is Loose Coupling ?

In simple words, loose coupling means they are mostly independent. If the only knowledge that class A has about class B, is what class B has exposed through its interface, then class A and class B are said to be loosely coupled. In order to overcome from the problems of tight coupling between objects, spring framework uses dependency injection mechanism with the help of a POJO/POJI model. Needless to say, through dependency injection its possible to achieve loose coupling.

Maximize Cohesion

The Cohesion of a single module/component is the degree to which its responsibilities form a meaningful unit; higher cohesion is better. We should group the related functionalities as to share a single responsibility (e.g. in a class).

In general, Cohesion is most closely associated with making sure that a class is designed with a single, well-focused purpose. The more focused a class is, the cohesiveness of that class is more. The advantages of high cohesion is that such classes are much easier to maintain (and less frequently changed) than classes with low cohesion. Another benefit of high cohesion is that classes with a well-focused purpose tend to be more reusable than other classes.

Suppose we have a class that multiply two numbers, but the same class creates a pop-up window displaying the result. This is the example of low cohesive class because the window and the multiplication operation don’t have much in common. To make it high cohesive, we would have to create a class Display and a class Multiply. The Display will call Multiply’s method to get the result and display it. Therefore, this could be an example to develop a high cohesive solution within OOPs Design Principles.

KISS (Keep It Simple, Stupid)

The Keep it Simple, Stupid (KISS) principle states that most systems work the best if they are kept simpler rather than made complex. Therefore, we should consider the simplicity as a key goal in the design, and avoid the unnecessary complication.

The Keep it Simple, Stupid (KISS) principle is a reminder to keep your code simple and readable for humans. If your method handles multiple use-cases, split them into smaller methods. If it performs multiple functionalities, make multiple methods instead.

Furthermore, if a single method handles multiple functionalities, it will become long and bulky. A long method will be very hard to maintain for programmers. Consequently, bugs will be harder to find, and we might find ourselves violating other design principles as well. If a method does two things, you can’t call it to do just one of them, so you’ll obviously make another method.

Also, you should keep your code simple to be easily understood by other developers. Learning of  OOPs Design Principles can also help you to achieve this. For example, if a simple for loop does the job efficiently, you should not use a stream API unnecessarily.

Delegation Principles

Don’t do all stuff by yourself, delegate it to the respective classes. A classical example of the delegation design principle is equals() and hashCode() method in Java. In order to compare two objects for equality, we ask the class itself to make comparison instead of the Client class doing that check.

The key benefit of this OOPs Design Principles is no duplication of code and pretty easy to modify behavior. Event delegation is another example of this principle, where an event is delegated to handlers for handling.

Encapsulate What Changes

Only one thing is constant in the software field, and that is “Change,” So encapsulate the code you expect or suspect to be changed in the future. The benefit of this OOP Design principles is that It’s easy to test and maintain proper encapsulated code.

YAGNI (you aren’t gonna need it)

YAGNI stands for “you aren’t gonna need it”: don’t implement something until it is necessary. Always implement things when you actually need them, never when you just foresee that you need them. It leads to code bloat; the software becomes larger and more complicated. The YAGNI principle suggests that developers should avoid adding unnecessary functionality or code that is not currently needed. By focusing on the current requirements and keeping the code simple, developers can improve the overall quality of the software.

The YAGNI principle can help developers avoid wasting time on developing features that may never be used. Instead, developers should focus on delivering software that meets the current requirements and can be easily maintained and extended in the future if necessary.

 

 

No comments:

Post a Comment