Single Responsibility Principle in Java with Example

Intent/Definition

Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
There should never be more than one reason for a class to change.

The Single Responsibility Principle represents the “S” of the five SOLID Principles of object-oriented programming to write well-designed code that is more readable, maintainable, and easier to upgrade and modify.

Rules of Thumb?

  • If you cannot come up with a meaningful name for your class focused on single responsibility, then it's probably doing too much.
  • Every object in our web application should have a single responsibility, and all object's services should be focused on carrying that single responsibility(SRP).
  • If you put more than one functionality in one Class in Java it introduces coupling between two functionality and even if you change one functionality there is a chance you broke coupled functionality, which requires another round of testing to avoid any surprise on the production environment.

Single Responsibility Principle Example

Let's see bad code design then we will see how to improve it using this principle.
In this example, we have a sample User Registration example.
When user register to the System then System will send mail t the user for verification.

Bad code design

First, let's see "bad" design and implementation. In bad design shows the functionalities like save user to the database and sending mail to user email address for verification mixed in a single class.
Below is an example which violates the Single Responsibility Principle.

User.java

public class User {
    private String firstName;
    private String lastName;
    private String email;
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

IUserService.java

public interface IUserService {
    public void registerUser(User user);
}

UserService.java

public class UserService implements IUserService {

    public void registerUser(User user) {
        // save user to database
        // send mail to user for verfication

        final String fromEmail = user.getEmail(); // requires valid gmail             // id
        final String password = "mypassword"; // correct password for gmail id
        final String toEmail = "[email protected]"; // can be any email id

        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com"); // SMTP Host
        props.put("mail.smtp.port", "587"); // TLS Port
        props.put("mail.smtp.auth", "true"); // enable authentication
        props.put("mail.smtp.starttls.enable", "true"); // enable STARTTLS

        // create Authenticator object to pass in Session.getInstance argument
        Authenticator auth = new Authenticator() {
            // override the getPasswordAuthentication method
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(fromEmail, password);
            }
        };
        Session session = Session.getInstance(props, auth);
        sendEmail(session, toEmail, "TLSEmail Testing Subject", "TLSEmail Testing Body");
   }

    private void sendEmail(Session session, String toEmail, String subject, String body) {
     try {
            MimeMessage msg = new MimeMessage(session);
            // set message headers
            msg.addHeader("Content-type", "text/HTML; charset=UTF-8");
            msg.addHeader("format", "flowed");
            msg.addHeader("Content-Transfer-Encoding", "8bit");

            msg.setFrom(new InternetAddress("[email protected]", "NoReply-JD"));

            msg.setReplyTo(InternetAddress.parse("[email protected]", false));

            msg.setSubject(subject, "UTF-8");

            msg.setText(body, "UTF-8");

            msg.setSentDate(new Date());

            msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail, false));
            System.out.println("Message is ready");
            Transport.send(msg);

            System.out.println("EMail Sent Successfully!!");
        } catch (Exception e) {
                e.printStackTrace();
        }
    }
}
Note that in above code, the User services and Email services are combined in a single class and hence it violates the Single Responsibility Principle.

Good code design

Let's refactor the code to make "good" design using SRP?
According to this principle, we need to separate out email functionality and its responsibility should be entirely encapsulated by the class. Below is an example to demonstrate the Single Responsibility Principle.

Class diagram

Let's write source code according to a class diagram.

User.java

public class User {
    private String firstName;
    private String lastName;
    private String email;
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    } 
}

IUserService.java

public interface IUserService {
    public void registerUser(User user);
}

UserService.java

public class UserService implements IUserService {

    private EmailInfo emailInfo;
    private IEmailService emailService;
    public void registerUser(User user) {
        // save user to database
        // send mail to user for verification.
  
        emailInfo = new EmailInfo("some subject", "some body", user.getEmail());
        emailService = new EmailService();
        emailService.sendEmail(emailInfo);
    }
}

EmailInfo.java

public class EmailInfo {
    private String subject;
    private String body;
    private String email;
    public EmailInfo(String subject, String body, String email) {
        super();
        this.subject = subject;
        this.body = body;
        this.email = email;
    }
    // add required fields for advanced mailing...
    public String getSubject() {
        return subject;
    }
    public void setSubject(String subject) {
        this.subject = subject;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

IEmailService.java

public interface IEmailService {
    void sendEmail(EmailInfo emailInfo);
}

EmailService.java

public class EmailService implements IEmailService {
 
    private final static String password = "mypassword"; // correct password for gmail id
    private final static String fromEmail = "[email protected]"; // can be any email id
    public void sendEmail(EmailInfo emailInfo) {

        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com"); // SMTP Host
        props.put("mail.smtp.port", "587"); // TLS Port
        props.put("mail.smtp.auth", "true"); // enable authentication
        props.put("mail.smtp.starttls.enable", "true"); // enable STARTTLS

        // create Authenticator object to pass in Session.getInstance argument
        Authenticator auth = new Authenticator() {
         // override the getPasswordAuthentication method
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(fromEmail, password);
            }
         };
        Session session = Session.getInstance(props, auth);
        sendEmail(session, emailInfo.getEmail(), emailInfo.getSubject(), emailInfo.getBody());
    }
 
    private void sendEmail(Session session, String toEmail, String subject, String body) {
     try {
          MimeMessage msg = new MimeMessage(session);
          // set message headers
          msg.addHeader("Content-type", "text/HTML; charset=UTF-8");
          msg.addHeader("format", "flowed");
          msg.addHeader("Content-Transfer-Encoding", "8bit");

          msg.setFrom(new InternetAddress("[email protected]", "NoReply-JD"));

          msg.setReplyTo(InternetAddress.parse("[email protected]", false));

          msg.setSubject(subject, "UTF-8");

          msg.setText(body, "UTF-8");

          msg.setSentDate(new Date());

          msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail, false));
          System.out.println("Message is ready");
          Transport.send(msg);

          System.out.println("EMail Sent Successfully!!");
        } catch (Exception e) {
           e.printStackTrace();
        }
    }
}
Note that in the above code, the responsibility of EmailService is separated from UserService so now UserService does its responsibility related user operations and EmailService does it's responsibility related email operations.
Do comment if you like this post or give us a suggestion if any improvements needed.

Posts Related to SOLID Principles

Learn complete Oops concepts and SOLID Principles on Object Oriented Design in Java Tutorial
Learn beginners to expert Core Java on Java Tutorial (300 + Articles)
You can find all the top tutorials of this site on Java/J2EE Tutorials on JavaGuides

Comments

  1. Wow ! ..Beautifully explained with Real word examples.

    ReplyDelete
  2. I've got an simple example below please have a look at the generateId() method and let me know is it being violate the SRP?

    public String generateId() {
    String ramdomValue = UUID.randomUUID().toString();
    String prefixValue = getPrefixFromConfig();
    return String.format("%s-%s", prefixValue, ramdomValue)
    }

    private String getPrefixFromConfig() {
    //get prefix value set from configuration file
    }

    If we try to refactor/decoupling code then our code will be more fit to the SRP, is it correct? Please advice!

    ReplyDelete

Post a Comment

Leave Comment