인터페이스 분리 원칙(Interface Segregation Principle)은 객체 지향 설계의 SOLID 원칙 중 하나로 클라이언트는 사용하지 않는 메서드에 의존하지 않아야 한다는 것을 의미. 이는 인터페이스를 설계할 때, 클라이언트가 필요로 하는 기능만 제공하도록 작은 인터페이스로 나누어야 한다는 원칙.
ISP 위반
// 하나의 큰 인터페이스
public interface Worker {
void work();
void attendMeeting();
void prepareReport();
}
// 모든 Worker가 이 인터페이스를 구현해야 함
public class Developer implements Worker {
@Override
public void work() {
System.out.println("코드를 작성합니다.");
}
@Override
public void attendMeeting() {
// 불필요한 메서드
System.out.println("회의 참석");
}
@Override
public void prepareReport() {
// 불필요한 메서드
System.out.println("보고서 작성");
}
}
ISP 준수
// 하나의 큰 인터페이스
public interface Worker {
void work();
void attendMeeting();
void prepareReport();
}
// 모든 Worker가 이 인터페이스를 구현해야 함
public class Developer implements Worker {
@Override
public void work() {
System.out.println("코드를 작성합니다.");
}
@Override
public void attendMeeting() {
// 불필요한 메서드
System.out.println("회의 참석");
}
@Override
public void prepareReport() {
// 불필요한 메서드
System.out.println("보고서 작성");
}
}
즉, 불필요한 코드 변경을 최소화하여 유지보수가 쉬워지고, 각 인터페이스가 독립적으로 설계되므로 재사용 가능성이 높아짐. 또한 작은 인터페이스로 나눔으로써 클래스가 결합도가 낮아짐. + 테스트 시에도 필요한 부분만 검증할 수 있음.
but, 지나치게 인터페이스를 작게 나누면 관리 비용이 증가할 수 있음.
요구사항: 간단한 도서 관리 시스템을 만들어 봅니다.
ISP 위반 코드 작성:
BookService에 모든 메서드 포함.public interface BookService {
void addBook(Book book);
void updateBook(Book book);
void deleteBook(Long bookId);
List<Book> getAllBooks();
Book findBookById(Long bookId);
}
ISP 준수 코드 작성:
public interface BookManagementService {
void addBook(Book book);
void updateBook(Book book);
void deleteBook(Long bookId);
}
public interface BookQueryService {
List<Book> getAllBooks();
Book findBookById(Long bookId);
}
실제 구현:
BookManagementService는 관리자를 위한 서비스에 구현.BookQueryService는 일반 사용자 API에 구현.Spring에서 Service와 Repository 계층에 대해 인터페이스 분리 원칙을 적용해 봅니다.
요구사항: 사용자 관리 시스템 구현.
ISP 준수 코드:
public interface UserManagementService {
void createUser(User user);
void updateUser(User user);
void deleteUser(Long userId);
}
public interface UserQueryService {
User findUserById(Long userId);
List<User> findAllUsers();
}
실제 구현:
UserManagementServiceImpl과 UserQueryServiceImpl 클래스를 각각 생성하여 구현.요구사항: 인증 및 권한 관리 시스템 구현.
ISP 준수 설계:
public interface AuthenticationService {
User authenticate(String username, String password);
}
public interface AuthorizationService {
void assignRole(Long userId, String role);
List<String> getRoles(Long userId);
}
실제 구현:
UserDetailsService를 활용하여 AuthenticationService 구현.AuthorizationService를 별도로 작성하여 권한 관리.추가 연습:
요구사항: 결제 시스템 설계.
ISP 준수 설계:
public interface CardPaymentService {
void payByCard(String cardNumber, double amount);
void cancelPayment(String transactionId);
}
public interface PointPaymentService {
double checkBalance(Long userId);
void usePoints(Long userId, double amount);
}
실제 구현:
CardPaymentService는 카드 결제 관련 API 호출 로직 작성.PointPaymentService는 포인트 잔액 관리 로직 작성.확장 연습:
요구사항: 모든 서비스 호출에 대해 로깅(Log) 기능 추가.
ISP 준수 코드:
AOP 적용:
@Aspect로 작성하여 분리된 인터페이스의 구현체에 적용.추가 실습:
위 실습을 통해 인터페이스 분리 원칙을 적용한 유연한 설계를 경험할 수 있습니다. 실무에서도 자주 발생하는 설계 문제를 해결하면서 코드 품질을 향상시키는 연습이 됩니다. 작은 기능부터 시작해 점차 복잡한 시스템으로 확장해보세요!