-
Interface Segregation Principle (ISP)Modeling/DesignPattern 2020. 2. 28. 11:14
1. Overview
A client should not be forced to depend upon an interface that they do not use.
2. Description
2.1 Intuition
2.1.1 Interface Pollution
- Large Interfaces
- Unrelated Methods
- Classes have empty method implementations
- Method implementations throw UnsupportedOperationsException or similar
- Method implementations return null default/dummy values
3. Example
3.1 Violate ISP
interface PersistenceService<T extends Entity> { public void save(T entity); public void delete(T entity); public T findById(Long id); public List<T> findByName(String name); } class Entity { private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } } class User extends Entity { private String name; private LocalDateTime lastLogin; public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDateTime getLastLogin() { return lastLogin; } public void setLastLogin(LocalDateTime lastLogin) { this.lastLogin = lastLogin; } } class Order extends Entity { private LocalDateTime orderPlaceOn; private double totalValue; public LocalDateTime getOrderPlaceOn() { return orderPlaceOn; } public void setOrderPlaceOn(LocalDateTime orderPlaceOn) { this.orderPlaceOn = orderPlaceOn; } public double getTotalValue() { return totalValue; } public void setTotalValue(double totalValue) { this.totalValue = totalValue; } } class UserPersistenceService implements PersistenceService<User> { private static final Map<Long, User> USERS = new HashMap<>(); @Override public void save(User entity) { synchronized (USERS) { USERS.put(entity.getId(), entity); } } @Override public void delete(User entity) { synchronized (USERS) { USERS.remove(entity.getId()); } } @Override public User findById(Long id) { synchronized (USERS) { return USERS.get(id); } } @Override public List<User> findByName(String name) { synchronized (USERS) { return USERS.values().stream().filter(u -> u.getName().equalsIgnoreCase(name)).collect(Collectors.toList()); } } } class OrderPersistenceService implements PersistenceService<Order>{ private static final Map<Long, Order> ORDERS = new HashMap<>(); @Override public void save(Order entity) { synchronized (ORDERS) { ORDERS.put(entity.getId(), entity); } } @Override public void delete(Order entity) { synchronized (ORDERS) { ORDERS.remove(entity.getId()); } } @Override public Order findById(Long id) { synchronized (ORDERS) { return ORDERS.get(id); } } // name doesn't make any sense for an order entity @Override public List<Order> findByName(String name) { throw new UnsupportedOperationException("Find by name is not suppored"); } }
3.2 Follow ISP
interface PersistenceService<T extends Entity> { public void save(T entity); public void delete(T entity); public T findById(Long id); // public List<T> findByName(String name); } class Entity { private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } } class User extends Entity { private String name; private LocalDateTime lastLogin; public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDateTime getLastLogin() { return lastLogin; } public void setLastLogin(LocalDateTime lastLogin) { this.lastLogin = lastLogin; } } class Order extends Entity { private LocalDateTime orderPlaceOn; private double totalValue; public LocalDateTime getOrderPlaceOn() { return orderPlaceOn; } public void setOrderPlaceOn(LocalDateTime orderPlaceOn) { this.orderPlaceOn = orderPlaceOn; } public double getTotalValue() { return totalValue; } public void setTotalValue(double totalValue) { this.totalValue = totalValue; } } class UserPersistenceService implements PersistenceService<User> { private static final Map<Long, User> USERS = new HashMap<>(); @Override public void save(User entity) { synchronized (USERS) { USERS.put(entity.getId(), entity); } } @Override public void delete(User entity) { synchronized (USERS) { USERS.remove(entity.getId()); } } @Override public User findById(Long id) { synchronized (USERS) { return USERS.get(id); } } // @Override public List<User> findByName(String name) { synchronized (USERS) { return USERS.values().stream().filter(u -> u.getName().equalsIgnoreCase(name)).collect(Collectors.toList()); } } } class OrderPersistenceService implements PersistenceService<Order>{ private static final Map<Long, Order> ORDERS = new HashMap<>(); @Override public void save(Order entity) { synchronized (ORDERS) { ORDERS.put(entity.getId(), entity); } } @Override public void delete(Order entity) { synchronized (ORDERS) { ORDERS.remove(entity.getId()); } } @Override public Order findById(Long id) { synchronized (ORDERS) { return ORDERS.get(id); } } // name doesn't make any sense for an order entity // @Override // public List<Order> findByName(String name) { // throw new UnsupportedOperationException("Find by name is not suppored"); // } }
one solution is to break your interfaces there is. Second is that if a method in an interface is applicable to only a single class then we can either remove that definition and put that only in the class for which it actually makes sense.
4. Reference
https://en.wikipedia.org/wiki/Interface_segregation_principle
'Modeling > DesignPattern' 카테고리의 다른 글
Flyweight (0) 2020.02.28 Dependency Inversion Principle (DIP) (0) 2020.02.28 Liskov Substitution Principle (LSP) (0) 2020.02.28 Open-Closed Principle (OCP) (0) 2020.02.26 Single Responsibility Principle (SRP) (0) 2020.02.26