SOLID Principles in Java

SOLID Principles in Java

In this article we will talk about one of the most important topic in software field ,

Classes are the building blocks of any java application. If these blocks are not strong, the building application is going to face the tough time in future. This essentially means that not so well written can lead to very difficult situations when the application scope goes up or application faces certain design issues either in production or maintenance.

On the other hand, set of well designed and written classes can speed up the coding process by leaps and bounds, while reducing the number of bugs in comparison.

In this Article , We will discuss SOLID principles in Java with examples which are 5 most recommended design principles, we should keep in mind while writing our classes. They also form the best practices to be followed for designing our application classes.

SOLID is one of the most popular sets of design principles in object oriented software development. It’s a mnemonic acronym for the following five design principles:

  • Single Responsibility Principle
  • Open/Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion

All of them are broadly used and worth knowing. But in this first post of my series about the SOLID principles, I will focus on the first one:

the Single Responsibility Principle.

“One class should have one and only one responsibility”


we should write, change and maintain a class for only one purpose. If it is model class then it should strictly represent only one entity. This will give we the flexibility to make changes in future without worrying the impacts of changes for another entity.

If you build your software over a longer period and if you need to adapt it to changing requirements, it might seem like the easiest and fastest approach is adding a method or functionality to your existing code instead of writing a new class or component. But that often results in classes with more than responsibility and makes it more and more difficult to maintain the software.

You can avoid these problems by asking a simple question before you make any changes: What is the responsibility of your class/component/microservice?

If your answer includes the word “and”, you’re most likely breaking the single responsibility principle. Then it’s better take a step back and rethink your current approach. There is most likely a better way to implement it.

 Example

The last example to talk about is the Spring Data repository. It implements the repository pattern and provides the common functionality of create, update, remove, and read operations. The repository adds an abstraction on top of the EntityManager with the goal to make JPA easier to use and to reduce the required code for these often used features.

You can define the repository as an interface that extends a Spring Data standard interface, e.g., RepositoryCrudRepository, or PagingAndSortingRepository. Each interface provides a different level of abstraction, and Spring Data uses it to generate implementation classes that provide the required functionality.

The following code snippet shows a simple example of such a repository. The AuthorRepository extends the Spring CrudRepository interface and defines a repository for an Author entity that uses an attribute of type Long as its primary key.

interface AuthorRepository extends CrudRepository<Author, Long> { 
    List findByLastname(String lastname); 
}

Spring’s CrudRepository provides standard CRUD operations, like a save and delete method for write operations and the methods findById and findAll to retrieve one or more Author entities from the database. The AuthorRepository also defines the findByLastName method, for which Spring Data generates the required JPQL query to select Author entities by their lastname attribute.

Each repository adds ready to use implementations of the most common operations for one specific entity. That is the only responsibility of that repository. the repository is not responsible for validation, authentication or the implementation of any business logic. It’s also not responsible for any other entities. This reduces the number of required changes and makes each repository easy to understand and implement.

Open Closed Principle.

“Software components should be open for extension, but closed for modification”

If we take a look into any good framework like struts or spring, we will see that we can not change their core logic and request processing, but we modify the desired application flow just by extending some classes and plugin them in configuration files.

A class is closed, since it may be compiled, stored in a library, baselined, and used by client classes. But it is also open, since any new class may use it as parent, adding new features. When a descendant class is defined, there is no need to change the original or to disturb its clients.

Example

spring framework has class DispatcherServlet. This class acts as front controller for String based web applications. To use this class, we are not required to modify this class. All we need is to pass initialization parameters and we can extend it’s functionality the way we want.

Please note that apart from passing initialization parameters during application startup, we can override methods as well to modify the behavior of target class by extending the classes. For example, struts Action classes are extended to override the request processing logic.

Extending Struts Action

public class HelloWorldAction extends Action
{
    @Override
    public ActionForward execute(ActionMapping mapping,
                                ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
                                throws Exception
    {
         
        //Process the request
 
    }
}

Liskov’s Substitution Principle.

“Derived types must be completely substitutable for their base types”

It means that the classes fellow developer created by extending our class should be able to fit in application without failure. This requires the objects of your subclasses to behave in the same way as the objects of your superclass. This is mostly seen in places where we do run time type identification and then cast it to appropriate reference type.

The principle defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass.

An example of LSP can be custom property editors in Spring framework. Spring provides property editors to represent properties in a different way than the object itself e.g. parsing human readable inputs from HTTP request parameters or displaying human readable values of pure java objects in view layer e.g. Currency or URL.

Spring can register one property editor for one data type and it is required to follow the constraint mandated by base class PropertyEditorSupport. So is any class extend PropertyEditorSupport class, then it can be substituted by everywhere base class is required.

Example

,every book has an ISBN number which is in always a fixed display format. You can have separate representations of ISBN in database and UI. For this requirement, we may write property editor in such a way –

IsbnEditor.java


import java.beans.PropertyEditorSupport;
import org.springframework.util.StringUtils;
import com.howtodoinjava.app.model.Isbn;
  
public class IsbnEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (StringUtils.hasText(text)) {
            setValue(new Isbn(text.trim()));
        } else {
            setValue(null);
        }
    }
  
    @Override
    public String getAsText() {
        Isbn isbn = (Isbn) getValue();
        if (isbn != null) {
            return isbn.getIsbn();
        } else {
            return "";
        }
    }
}

Interface Segregation Principle

“Clients should not be forced to implement unnecessary methods which they will not use”

Sounds obvious, doesn’t it? Well, as I will show you in this article, it’s pretty easy to violate this interface, especially if your software evolves and you have to add more and more features. But more about that later.

Similar to the Single Responsibility Principle, the goal of the Interface Segregation Principle is to reduce the side effects and frequency of required changes by splitting the software into multiple, independent parts.

Example

The best place to look for IPS examples is Java AWT event handlers for handling GUI events fired from keyboard and mouse. It has different listener classes for each kind of event. We only need to write handlers for events, we wish to handle. Nothing is mandatory.

Some of the listeners are –

  • FocusListener
  • KeyListener
  • MouseMotionListener
  • MouseWheelListener
  • TextListener
  • WindowFocusListener

Anytime, we wish to handle any event, just find out corresponding listener and implement it.

MouseMotionListenerImpl.java


public class MouseMotionListenerImpl implements MouseMotionListener
{
    @Override
    public void mouseDragged(MouseEvent e) {
        //handler code
    }
 
    @Override
    public void mouseMoved(MouseEvent e) {
        //handler code
    }
}

Dependency Inversion Principle.

“Depend on abstractions, not on concretions”

The general idea of this principle is as simple as it is important: High level modules, which provide complex logic, should be easily reusable and unaffected by changes in low level modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the high level and low level modules from each other.An important detail of this definition is, that high level and low level modules depend on the abstraction. The design principle does not just change the direction of the dependency, as you might have expected when you read its name for the first time. It splits the dependency between the high level and low level modules by introducing an abstraction between them. So in the end, you get two dependencies:

  1. the high level module depends on the abstraction, and
  2. the low level depends on the same abstraction.

This might sound more complex than it often is. If you consequently apply the Open/Closed Principle and the Liskov Substitution Principle to your code, it will also follow the Dependency Inversion Principle.

The Open/Closed Principle required a software component to be open for extension, but closed for modification. You can achieve that by introducing interfaces for which you can provide different implementations. The interface itself is closed for modification, and you can easily extend it by providing a new interface implementation.

Your implementations should follow the Liskov Substitution Principle so that you can replace them with other implementations of the same interface without breaking your application.

we will take a full example to achieve all 5 principles in the next Article .





要查看或添加评论,请登录

Ahmed marey的更多文章

  • Microservices < Resilient and Fault Tolerant >

    Microservices < Resilient and Fault Tolerant >

    In a Monolith application, a single error has the potential of bringing down the entire application. This can be…

  • map and flatMap methods in Java 8 .

    map and flatMap methods in Java 8 .

    In Java, Stream interface has >map() and flatMap() methods and both are intermediate stream operations and return…

  • Aspect-Oriented Programming (Spring AOP)

    Aspect-Oriented Programming (Spring AOP)

    In this article , we will talk about aspect oriented programming programming (AOP) definition and implement it using…

  • springboot Microservice Example .

    springboot Microservice Example .

    In this article, we’ll discuss quick example of the most common concept in Spring (BOOT , CLOUD) , we will start…

  • Monolithic , Microservice Architecture

    Monolithic , Microservice Architecture

    In this article,we will talk about Monolithic , Microservice Architecture and the difference between them . Monolith…

  • SpringBoot Asynchronous Methods

    SpringBoot Asynchronous Methods

    In this article,we will talk about SpringBoot Asynchronous Methods , We will annotate a bean method; @Async will make…

社区洞察

其他会员也浏览了