-
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
'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