ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Open-Closed Principle (OCP)
    Modeling/DesignPattern 2020. 2. 26. 01:46

    1. Overview

    Software entities such as Classes, Modules, Methods, and etc, should be open for extension but closed for modification.

    2. Intuition

    Open for Extension: Extend existing behavior

    Closed for Modification: Existing code remains unchanged

    3. Example

    3.1 Violate OCP

    abstract class Subscriber {}
    class CallHistory {
        public static class Call {
            private LocalDateTime begin;
            private long duration;
            private Long subscriberId;
            public Call(Long subscriberId, LocalDateTime begin, long duration) {
                this.begin = begin;
                this.duration = duration;
                this.subscriberId = subscriberId;
            }
            public LocalDateTime getBegin() {
                return begin;
            }
            public long getDuration() {
                return duration;
            }
            public Long getSubscriberId() {
                return subscriberId;
            }
        }
        private static final Map<Long, List<Call>> CALLS = new Hashtable<>();
        public static List<Call> getCurrentCalls(Long subscriberId) {
            if(!CALLS.containsKey(subscriberId)) {
                return Collections.emptyList();
            }
            return CALLS.get(subscriberId);
        }
        public static void addSession(Long subscriberId, LocalDateTime begin, long duration) {
            List<Call> calls;
            if(!CALLS.containsKey(subscriberId)) {
                calls =  new LinkedList<>();
                CALLS.put(subscriberId, calls);
            } else {
                calls = CALLS.get(subscriberId);
            }
            calls.add(new Call(subscriberId, begin, duration));
        }
    }
    class InternetSessionHistory {
        public static class InternetSession {
            private LocalDateTime begin;
            private Long subscriberId;
            private Long dataUsed;
            public InternetSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
                this.begin = begin;
                this.dataUsed = dataUsed;
                this.subscriberId = subscriberId;
            }
            public LocalDateTime getBegin() {
                return begin;
            }
            public long getDataUsed() {
                return dataUsed;
            }
            public Long getSubscriberId() {
                return subscriberId;
            }
        }
        private static final Map<Long, List<InternetSession>> SESSIONS = new HashMap<>();
        public synchronized static List<InternetSession> getCurrentSessions(Long subscriberId) {
            if(!SESSIONS.containsKey(subscriberId)) {
                return Collections.emptyList();
            }
            return SESSIONS.get(subscriberId);
        }
        public synchronized static void addSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
            List<InternetSession> sessions;
            if(!SESSIONS.containsKey(subscriberId)) {
                sessions = new LinkedList<>();
                SESSIONS.put(subscriberId, sessions);
            } else {
                sessions = SESSIONS.get(subscriberId);
            }
            sessions.add(new InternetSession(subscriberId, begin, dataUsed));
        }
    }
    class ISPSubscriber {
        // Duplication fields
        private Long subscriberId;
        private String address;
        private Long phoneNumber;
        private int baseRate;
        private long freeUsage;
        public ISPSubscriber() {
    
        }
        // Common functionality
        public double calculateBill() {
            List<InternetSessionHistory.InternetSession> sessions = InternetSessionHistory.getCurrentSessions(subscriberId);
            long totalData = sessions.stream().mapToLong(InternetSessionHistory.InternetSession::getDataUsed).sum();
            long chargeableData = totalData - freeUsage;
            return chargeableData*baseRate/100;
        }
        public Long getSubscriberId() {
            return subscriberId;
        }
        public void setSubscriberId(Long subscriberId) {
            this.subscriberId = subscriberId;
        }
        public String getAddress() {
            return address;
        }
        public void setAddress(String address) {
            this.address = address;
        }
        public Long getPhoneNumber() {
            return phoneNumber;
        }
        public void setPhoneNumber(Long phoneNumber) {
            this.phoneNumber = phoneNumber;
        }
        public int getBaseRate() {
            return baseRate;
        }
        public void setBaseRate(int baseRate) {
            this.baseRate = baseRate;
        }
        public long getFreeUsage() {
            return freeUsage;
        }
        public void setFreeUsage(long freeUsage) {
            this.freeUsage = freeUsage;
        }
    }
    class PhoneSubscriber {
        // Duplication fields
        private Long subscriberId;
        private String address;
        private Long phoneNumber;
        private int baseRate;
        // Common functionality
        public double calculateBill() {
            List<CallHistory.Call> sessions = CallHistory.getCurrentCalls(subscriberId);
            long totalDuration = sessions.stream().mapToLong(CallHistory.Call::getDuration).sum();
            return totalDuration*baseRate/100;
        }
        public Long getSubscriberId() {
            return subscriberId;
        }
        public void setSubscriberId(Long subscriberId) {
            this.subscriberId = subscriberId;
        }
        public String getAddress() {
            return address;
        }
        public void setAddress(String address) {
            this.address = address;
        }
        public Long getPhoneNumber() {
            return phoneNumber;
        }
        public void setPhoneNumber(Long phoneNumber) {
            this.phoneNumber = phoneNumber;
        }
        public int getBaseRate() {
            return baseRate;
        }
        public void setBaseRate(int baseRate) {
            this.baseRate = baseRate;
        }
    }

    3.2 Follow OCP

    abstract class Subscriber {
        // Aggregate common fields
        protected Long subscriberId;
        protected String address;
        protected Long phoneNumber;
        public Long getSubscriberId() {
            return subscriberId;
        }
        public void setSubscriberId(Long subscriberId) {
            this.subscriberId = subscriberId;
        }
        public String getAddress() {
            return address;
        }
        public void setAddress(String address) {
            this.address = address;
        }
        public Long getPhoneNumber() {
            return phoneNumber;
        }
        public void setPhoneNumber(Long phoneNumber) {
            this.phoneNumber = phoneNumber;
        }
        // Aggregate common functionality
        public abstract double calculateBill();
    }
    class CallHistory {
        public static class Call {
            private LocalDateTime begin;
            private long duration;
            private Long subscriberId;
            public Call(Long subscriberId, LocalDateTime begin, long duration) {
                this.begin = begin;
                this.duration = duration;
                this.subscriberId = subscriberId;
            }
            public LocalDateTime getBegin() {
                return begin;
            }
            public long getDuration() {
                return duration;
            }
            public Long getSubscriberId() {
                return subscriberId;
            }
        }
        private static final Map<Long, List<CallHistory.Call>> CALLS = new Hashtable<>();
        public static List<CallHistory.Call> getCurrentCalls(Long subscriberId) {
            if(!CALLS.containsKey(subscriberId)) {
                return Collections.emptyList();
            }
            return CALLS.get(subscriberId);
        }
        public static void addSession(Long subscriberId, LocalDateTime begin, long duration) {
            List<CallHistory.Call> calls;
            if(!CALLS.containsKey(subscriberId)) {
                calls =  new LinkedList<>();
                CALLS.put(subscriberId, calls);
            } else {
                calls = CALLS.get(subscriberId);
            }
            calls.add(new CallHistory.Call(subscriberId, begin, duration));
        }
    }
    class InternetSessionHistory {
    
        public static class InternetSession {
    
            private LocalDateTime begin;
    
            private Long subscriberId;
    
            private Long dataUsed;
    
            public InternetSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
                this.begin = begin;
                this.dataUsed = dataUsed;
                this.subscriberId = subscriberId;
            }
            public LocalDateTime getBegin() {
                return begin;
            }
            public long getDataUsed() {
                return dataUsed;
            }
            public Long getSubscriberId() {
                return subscriberId;
            }
    
        }
        private static final Map<Long, List<InternetSessionHistory.InternetSession>> SESSIONS = new Hashtable<>();
    
        public static List<InternetSessionHistory.InternetSession> getCurrentSessions(Long subscriberId) {
            if(!SESSIONS.containsKey(subscriberId)) {
                return Collections.emptyList();
            }
            return SESSIONS.get(subscriberId);
        }
    
        public static void addSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
            List<InternetSessionHistory.InternetSession> sessions;
            if(!SESSIONS.containsKey(subscriberId)) {
                sessions = new LinkedList<>();
                SESSIONS.put(subscriberId, sessions);
            } else {
                sessions = SESSIONS.get(subscriberId);
            }
            sessions.add(new InternetSessionHistory.InternetSession(subscriberId, begin, dataUsed));
        }
    }
    class ISPSubscriber extends Subscriber {
        private int baseRate;
        private long freeUsage;
        //only for demonstration
        @Override
        public double calculateBill() {
            List<InternetSessionHistory.InternetSession> sessions = InternetSessionHistory.getCurrentSessions(subscriberId);
            long totalData = sessions.stream().mapToLong(InternetSessionHistory.InternetSession::getDataUsed).sum();
            long chargeableData = totalData - freeUsage;
    
            if(chargeableData <= 0) {
                return 0;
            }
            return chargeableData*baseRate/100;
        }
        public int getBaseRate() {
            return baseRate;
        }
        public void setBaseRate(int baseRate) {
            this.baseRate = baseRate;
        }
        public long getFreeUsage() {
            return freeUsage;
        }
        public void setFreeUsage(long freeUsage) {
            this.freeUsage = freeUsage;
        }
    }
    class PhoneSubscriber extends Subscriber {
        private int baseRate;
        //only for demonstration - open for extension
        @Override
        public double calculateBill() {
            List<CallHistory.Call> sessions = CallHistory.getCurrentCalls(subscriberId);
            long totalDuration = sessions.stream().mapToLong(CallHistory.Call::getDuration).sum();
            return totalDuration*baseRate/100;
        }
        public int getBaseRate() {
            return baseRate;
        }
        public void setBaseRate(int baseRate) {
            this.baseRate = baseRate;
        }
    }

    4. Reference

    https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle

    'Modeling > DesignPattern' 카테고리의 다른 글

    Interface Segregation Principle (ISP)  (0) 2020.02.28
    Liskov Substitution Principle (LSP)  (0) 2020.02.28
    Single Responsibility Principle (SRP)  (0) 2020.02.26
    State  (0) 2020.02.26
    Strategy  (0) 2020.02.26

    댓글

Designed by Tistory.