도서 요약 / / 2023. 1. 30. 21:20

인터페이스 분리 원칙 : ISP(Interface Segregation Principle)

이 내용은 udemy의 SOLID Design Principles를 정리한 내용입니다.

https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/


Interface Segregation Principle에서 클라이언트는 사용하지 않는 인터페이스에 의존해서는 안된다라고 말하고 있다.

특히, 메소드에 대해서 말하고 있는 것이다. 클라이언트는 사용하지 않는 인터페이스에서 정의된 메소드를 의존해서는 안되고 인터페이스 오염에 대한 용어에 대해 이야기하고 있다.

이 용어를 들어보지 못했을 수도 있지만 이것이 의미하는 것은 인터페이스를 크게 만들지 말라는 것이다.

인터페이스 오염(Interface Pollution): 하나의 큰 인터페이스가 있고 모든 클래스가 그 인터페이스를 사용하고 있다면 이 원칙을 위반하게 되는 것이다.

Interface Pollution의 징후

  • 클래스에서 메소드 구현부가 없는 경우
  • 메소드 구현부가 UnsupportedOperationException과 유사한 동작을 하는 경우
  • 메소드 구현부가 null 이나 default/dummy 값을 리턴하는 경우

Interface Segregation Principle에서 응집도 있는(서로 관련이 있는 것들) 특정 인터페이스에서 정의된 메소드 혹은 동작을 잘게 쪼개라고 말하고 있는 것이다. 그러면 클래스에서 관련없는 메소드 구현을 사용해야 하는 상황을 만들지 않는다.

이것이 Interface Segregation Principle에 관한 내용이다.

예제

Interface Segregation Principle와 관련된 예제를 알아보자.

@Getter
@Setter
public class Entity {

    private Long id;

}
@Getter
@Setter
public class User extends Entity {

    private String name;

    private LocalDateTime lastLogin;
}
@Getter
@Setter
public class Order extends Entity {

    private LocalDateTime orderPlacedOn;

    private double totalValue;
}

여기서 사용자를 저장하는 UserPersistenceService가 있다.

public 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(u.getName()))
                .collect(Collectors.toList());
        }
    }
}

UserPersistenceService에는 4개의 메소드가 있다.

Interface Segregation Principle을 위반을 발견하는 가장 일반적인 방법은 기존 클래스에서 인터페이스를 추출하는 것이다.

누군가 UserPersistenceService를 사용할 목적으로 PersistenceService를 만들었다.

public interface PersistenceService<T extends Entity> {

    void save(T entity);

    void delete(T entity);

    T findById(Long id);

    List<T> findByName(String name);
}

이제 Order 엔티티가 있고 서비스를 만들어보자.

public 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);
        }
    }

    @Override
    public List<Order> findByName(String name) {
        throw new UnsupportedOperationException("Find by name is not supported");
    }
}

User는 name을 가지고 있어서 findByName이 의미가 있지만 Order의 경우 name이 없다. 그래서 Order의 경우 어떤 값도 리턴할 수가 없다. 그래서 UnsupportedOperationException으로 리턴을 할 수 밖에 없다.

이 문제를 어떻게 해결할 수 있나?

인터페이스에서 findByName을 쪼개는 방법이 하나의 해결책이다.

또 다른 해결책은 인터페이스의 메소드가 단일 클래스에만 적용이 가능하다면, 인터페이스에서 제거하고 클래스에 적용하는 것이다.

두 번째 해결책으로 적용해보면 아래와 같다.

public interface PersistenceService<T extends Entity> {

    void save(T entity);

    void delete(T entity);

    T findById(Long id);
}

PersistenceService에서 findByName을 제거한다.

public class UserPersistenceService implements PersistenceService<User> {

  // Override를 제거하고 UserPersistenceService에만 구현한다.
  public List<User> findByName(String name) {
    synchronized (USERS) {
      return USERS.values().stream()
        .filter(u -> u.getName().equalsIgnoreCase(u.getName()))
        .collect(Collectors.toList());
    }
  }
}

그리고 findByName의 @Override를 제거하고 UserPersistenceService에서만 findByName을 구현한다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유