-
Single Responsibility Principle (SRP)Modeling/DesignPattern 2020. 2. 26. 00:45
1. Overview
There should never be more than one reason for a class to change. A class is focused, single functionality, and addresses a specific concern.
2. Intuition
Guess an AwesomeClass which have communication protocol, message format, and authentication. When we have to change every HTTP to HTTPS, JSON to XML, and authentication, AwesomeClass should be changed.
So Single Responsibility means that whenever you're designing a class is more you should take care of that particular class is addressing only a specific concern so that when the time comes or a change comes down the pipeline then there is only one reason for a particular class to change.
3. Example
3.1 Violate SRP
class User { private String name; private String email; private String address; public User() {} public User(String name, String email, String address) { this.name = name; this.email = email; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User [name=" + name + ", email=" + email + ", address=" + address + "]"; } } class Store { private static final Map<String, User> STORAGE = new Hashtable<>(); public void store(User user) { synchronized(STORAGE) { STORAGE.put(user.getName(), user); } } public User getUser(String name) { synchronized(STORAGE) { return STORAGE.get(name); } } } class UserController { //Store used by controller private Store store = new Store(); //Create a new user public String createUser(String userJson) throws IOException { ObjectMapper mapper = new ObjectMapper(); User user = mapper.readValue(userJson, User.class); if(!isValidUser(user)) { return "ERROR"; } store.store(user); return "SUCCESS"; } //Validates the user object private boolean isValidUser(User user) { if(!isPresent(user.getName())) { return false; } user.setName(user.getName().trim()); if(!isValidAlphaNumeric(user.getName())) { return false; } if(user.getEmail() == null || user.getEmail().trim().length() == 0) { return false; } user.setEmail(user.getEmail().trim()); if(!isValidEmail(user.getEmail())) { return false; } return true; } //Simply checks if value is null or empty.. private boolean isPresent(String value) { return value != null && value.trim().length() > 0; } //check string for special characters private boolean isValidAlphaNumeric(String value) { Pattern pattern = Pattern.compile("[^A-Za-z0-9]"); Matcher matcher = pattern.matcher(value); return !matcher.find(); } //check string for valid email address - this is not for prod. //Just for demo. This fails for lots of valid emails. private boolean isValidEmail(String value) { Pattern pattern = Pattern.compile("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"); Matcher matcher = pattern.matcher(value); return matcher.find(); } } public class SRPDemo { private static final String VALID_USER_JSON = "{\"name\": \"Randy\", \"email\": \"randy@email.com\", \"address\":\"110 Sugar lane\"}"; private static final String INVALID_USER_JSON = "{\"name\": \"Sam\", \"email\": \"sam@email\", \"address\":\"111 Sugar lane\"}"; public static void main(String[] args) throws IOException { UserController controller = new UserController(); //Check with valid JSON String response = controller.createUser(VALID_USER_JSON); if(!response.equalsIgnoreCase("SUCCESS")) { System.err.println("Failed"); } System.out.println("Valid JSON received response: "+response); //Check with invalid JSON response = controller.createUser(INVALID_USER_JSON); if(!response.equalsIgnoreCase("ERROR")) { System.err.println("Failed"); } System.out.println("Invalid JSON received response: "+response); } }
3.2 Follow SRP
class User { private String name; private String email; private String address; public User() {} public User(String name, String email, String address) { this.name = name; this.email = email; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User [name=" + name + ", email=" + email + ", address=" + address + "]"; } } class Store { private static final Map<String, User> STORAGE = new Hashtable<>(); public void store(User user) { synchronized(STORAGE) { STORAGE.put(user.getName(), user); } } public User getUser(String name) { synchronized(STORAGE) { return STORAGE.get(name); } } } class UserController { private UserPersistenceService persistenceService = new UserPersistenceService(); //Create a new user public String createUser(String userJson) throws IOException { ObjectMapper mapper = new ObjectMapper(); User user = mapper.readValue(userJson, User.class); UserValidator validator = new UserValidator(); boolean valid = validator.validateUser(user); if(!valid) { return "ERROR"; } persistenceService.saveUser(user); return "SUCCESS"; } } class UserPersistenceService { private Store store = new Store(); public void saveUser(User user) { store.store(user); } } class UserValidator { public boolean validateUser(User user) { return isValidUser(user); } //Validates the user object private boolean isValidUser(User user) { if(!isPresent(user.getName())) { return false; } user.setName(user.getName().trim()); if(!isValidAlphaNumeric(user.getName())) { return false; } if(user.getEmail() == null || user.getEmail().trim().length() == 0) { return false; } user.setEmail(user.getEmail().trim()); if(!isValidEmail(user.getEmail())) { return false; } return true; } //Simply checks if value is null or empty.. private boolean isPresent(String value) { return value != null && value.trim().length() > 0; } //check string for special characters private boolean isValidAlphaNumeric(String value) { Pattern pattern = Pattern.compile("[^A-Za-z0-9]"); Matcher matcher = pattern.matcher(value); return !matcher.find(); } //check string for valid email address private boolean isValidEmail(String value) { Pattern pattern = Pattern.compile("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"); Matcher matcher = pattern.matcher(value); return matcher.find(); } } public class SRPValidDemo { private static final String VALID_USER_JSON = "{\"name\": \"Randy\", \"email\": \"randy@email.com\", \"address\":\"110 Sugar lane\"}"; private static final String INVALID_USER_JSON = "{\"name\": \"Sam\", \"email\": \"sam@email\", \"address\":\"111 Sugar lane\"}"; public static void main(String[] args) throws IOException { UserController controller = new UserController(); String response = controller.createUser(VALID_USER_JSON); if(!response.equalsIgnoreCase("SUCCESS")) { System.err.println("Failed"); } System.out.println("Valid JSON received response: "+response); response = controller.createUser(INVALID_USER_JSON); if(!response.equalsIgnoreCase("ERROR")) { System.err.println("Failed"); } System.out.println("Invalid JSON received response: "+response); } }
4. Reference
https://en.wikipedia.org/wiki/Single_responsibility_principle
'Modeling > DesignPattern' 카테고리의 다른 글
Liskov Substitution Principle (LSP) (0) 2020.02.28 Open-Closed Principle (OCP) (0) 2020.02.26 State (0) 2020.02.26 Strategy (0) 2020.02.26 Prototype (0) 2020.02.26