Software Design - Single Responsibility Principle

We will start with what Introduction and then proceed with example code and ends with benefits of Single Responsibility Principle.

1. Introduction

2. Example

Consider we have a task to write FileParser. It currently supports parsing CSV, TEXT, PDF, JSON file.

class FileParser {

public void parseFile(File file) {
// parse file logic for xml, csv, json data in files
if (isValidFile(file, FileType.CSV, FileType.XML, FileType.JSON)) {
//parsing logic starts
}
}

private boolean isValidFile(File file, FileType... types) {
if(file == null || types == null || types.length == 0)
return false;

String fileName = file.getName().toLowerCase();
for (FileType type : types) {
if (fileName.endsWith(type.getExtension()))
return true;
}

return false;
}

}
enum FileType { 

CSV(".csv"), XML(".xml"), JSON(".json"), PDF(".pdf"), RICHTEXT(".rtf"), TXT(".txt");

private String extension;

private FileType (String extension) {
this.extension = extension;
}

public String getExtension() {
return this.extension;
}
}

This FileParser class is doing more than one stuff.

In future, if we want to parse text, rtf type files and so on then we need to change this class. Also, if we want to change how we validate the file, then also we need to change this file. This leads to the problem like unit testing the class again because one change can affect the existing functionality and so on.

In the above example, if we want to change the strategy to parse xml file, then the file need to be changed. Let's say previously it was using dom parser to parse xml but we want to use SAX parser for parsing due to change in requirement or due to bigger size of file. Then, we need to change this class again. Same can happen with json or csv type parsing.

We can avoid multiple reasons for change in the FileParser class by introducing separate class for validating the file and also modify the structure of the class and introduce new classes for which have specific responsibility to parse specific type of class.

3. SRP to rescue

Let's update the FileParser to follow Single Responsibility Principle.

FileParser.java

/**
* @author Gaurav Rai Mazra
*
*/

public class FileParser {
private Parser parser;

public FileParser(Parser parser) {
this.parser = parser;
}

public void setParser(Parser parser) {
this.parser = parser;
}

public void parseFile(File file) {
if (FileValidationUtils.isValidFile(file, parser.getFileType())) {
parser.parse(file);
}
}
}

FileValidationUtils.java

/**
* @author Gaurav Rai Mazra
*
*/

public class FileValidationUtils {

private FileValidationUtils() { }

public static boolean isValidFile (File file, FileType... types) {
if (file == null || types == null || types.length == 0)
return false;

String fileName = file.getName().toLowerCase();
for (FileType type : types) {
if (fileName.endsWith(type.getExtension()))
return true;
}

return false;
}

public static boolean isValidFile (File file, FileType type) {
if (file == null || type == null)
return false;

String fileName = file.getName().toLowerCase();

return fileName.endsWith(type.getExtension());
}
}

FileType.java

/**
* @author Gaurav Rai Mazra
*
*/

public enum FileType {

CSV(".csv"), XML(".xml"), JSON(".json"), PDF(".pdf"), RICHTEXT(".rtf"), TXT(".txt");

private String extension;

private FileType (String extension) {
this.extension = extension;
}

public String getExtension() {
return this.extension;
}
}

Parser.java

/**
* @author Gaurav Rai Mazra
*
*/

public interface Parser {
//method to parse file
public void parse(File file);

// return filetype to validate
public FileType getFileType();
}

/**
* @author Gaurav Rai Mazra
*
*/

public class CSVFileParser implements Parser {

@Override
public void parse(File file) {
//logic to parse CSV file goes here
}

@Override
public FileType getFileType() {
return FileType.XML;
}

}

/**
* @author Gaurav Rai Mazra
*
*/

public class XmlFileParser implements Parser {

@Override
public void parse(File file) {
// logic to parse xml file
}

@Override
public FileType getFileType() {
return FileType.XML;
}

}

/**
* @author Gaurav Rai Mazra
*
*/

public class JsonFileParser implements Parser {

@Override
public void parse(File file) {
// Logic to parse json file
}

@Override
public FileType getFileType() {
return FileType.JSON;
}
}

In this FileParser class, we removed method to validate files which was present in earlier version and placed it in FileValidationUtils class. We removed the extra responsibility to validate files from this FileParser class.

We also removed the code which actually parses the file based on whatever file it is like xml, csv or json. Now, In case we need to change our xml reading/parsing logic that that can be changed without changing FileParser class. The solution here to divide the responsibility of parsing specific type to their respective classes and then those classes may have other specific method to parse those files.

In our solution, we created interface Parser and then created the specific classes to handle XML, CSV and JSON parsing like CSVFileParser, JsonFileParser and XmlFileParser.

We used composition relation in FileParser and give setter to change parsing at any time in this FileParser but the functionality will never change in FileParser class.

4. Benefits of SRP

This is how we can gain long-term benefits from SRP. The example code used above could be found at github.



Tags: SOLID principles, Single Responsiblity Principle, SRP, class design principles, Software Design

← Back home