ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

    댓글

Designed by Tistory.