Develop/Java

[Java] Service Implement 와 Interface를 사용하는 이유

DevPi 2024. 6. 29. 17:43
반응형

Java 프로젝트를 개발하다 보면 종종 Controller -> Service (로직처리) 구조가 아닌 Controller -> Service Interface -> Service Implement 구조를 사용하는 경우를 볼 수 있습니다. 이러한 구조가 왜 필요한지, 어떤 기준으로 이런 구조를 정하는지에 대한 궁금증을 풀어보겠습니다.


왜 Service Interface와 Service Implement를 사용하는가?

서비스 인터페이스와 구현체를 분리하는 패턴은 코드의 유연성과 유지보수성을 높이기 위한 설계 방식입니다. 이를 통해 얻을 수 있는 주요 이점은 다음과 같습니다.

 

1. 구현체의 교체 용이성

인터페이스를 사용하면 언제든지 구현체를 변경할 수 있습니다. 예를 들어, UserService 인터페이스를 여러 개의 클래스가 구현할 수 있으며, 특정 구현체를 다른 구현체로 쉽게 교체할 수 있습니다. 이는 구현체를 변경할 필요가 있을 때 매우 유용합니다.

// 변경된 구현체
public class UserServiceImpl implements UserService {
    @Override
    public String getUser() {
        return "Modified User";
    }

    public String getUserDetails() {
        return "User Details";
    }
}

// 새로운 구현체
public class AdvancedUserServiceImpl implements UserService {
    @Override
    public String getUser() {
        return "Advanced User";
    }

    public String getAdvancedUserDetails() {
        return "Advanced User Details";
    }
}

// 클라이언트 코드
public class UserClient {
    private UserService userService;

    public UserClient(UserService userService) {
        this.userService = userService;
    }

    public void displayUser() {
        System.out.println(userService.getUser());
    }

    public static void main(String[] args) {
        UserService userService = new AdvancedUserServiceImpl();
        UserClient client = new UserClient(userService);
        client.displayUser();
    }
}

// 서비스 코드
public interface UserService {
    void getUser();
}

 

위와 같이 인터페이스를 사용하면 UserService의 구현체를 UserServiceImpl에서 AdvancedUserServiceImpl로 쉽게 교체할 수 있습니다.

 

2. 테스트 용이성

인터페이스를 사용하면 테스트를 위해 쉽게 목(Mock) 객체를 만들 수 있습니다. 이는 단위 테스트에서 매우 유용합니다. 인터페이스를 사용하여 실제 구현체 대신 목 객체를 주입하면 테스트가 간편해지고, 의존성을 줄일 수 있습니다.

 

3. 확장성

새로운 기능을 추가할 때 기존 코드를 최소한으로 변경하면서 확장할 수 있습니다. 인터페이스를 사용하면 새로운 구현체를 추가하여 기존 시스템을 확장할 수 있습니다. 이는 시스템의 유지보수와 확장성을 높이는 데 큰 도움이 됩니다.

 

4. 유연한 설계

인터페이스를 사용하면 서로 다른 구현체를 다룰 수 있는 유연한 설계를 할 수 있습니다. 이는 다양한 구현체를 필요로 하는 상황에서 매우 유용합니다. 예를 들어, 다양한 데이터 소스에서 데이터를 가져와야 하는 경우, 각각의 데이터 소스를 위한 구현체를 인터페이스로 정의할 수 있습니다.

 

5. 인터페이스 변경의 최소화

인터페이스는 기능 명세(Contracts)를 정의하고, 변경이 자주 일어나지 않도록 설계되어야 합니다. 인터페이스가 변경될 때, 그 인터페이스를 구현하는 모든 클래스가 해당 변경에 따라 수정되어야 하므로, 인터페이스는 처음부터 잘 설계하는 것이 중요합니다.


 

꼭 인터페이스를 사용해야 할까?

 

이러한 설계 방식은 클라이언트 코드에서 바로 서비스 코드로 가는 과정을 인터페이스와 구현체로 나누었기 때문에 구조가 복잡해지면서 굳이 이렇게 해야 하나? 하는 의구심이 들 수 있습니다. 특히 인터페이스와 구현체가 1:1 관계로 이루어지는 설계에서는 이러한 장점들을 잘 살리지 못하기 때문입니다.

 

언제 변경될 지 모른다.

 

객체지향의 세계에서는 모든 것이 변한다고 생각해야 합니다. 개발은 한번 완성되면 끝인 것이 아닌, 사용자의 요구사항이나 추가 개발 사항에 따라 설계의 구조가 바뀌게 됩니다. 현재 인터페이스와 구현체 클래스가 1:1의 관계를 하고 있더라도 서비스가 확장됨에 따라 얼마든지 구현체 클래스는 증가할 수 있습니다. 그렇기에 인터페이스와 구현체 클래스를 분리하여 미래 변화에 유연하게 대처할 필요가 있습니다.


결론

인터페이스와 구현체를 분리함으로써 얻는 주요 이점은 구현체의 교체 용이성, 테스트 용이성, 확장성, 그리고 유연한 설계입니다. 인터페이스는 기능 명세를 정의하고, 이를 구현하는 구현체들은 독립적으로 변경할 수 있도록 함으로써 코드의 유지보수성과 유연성을 높입니다. 인터페이스 변경이 필요한 상황을 최소화하기 위해 처음부터 신중하게 설계하는 것이 중요합니다.

이와 같은 설계 방식은 객체지향 프로그래밍의 주요 원칙인 OCP (Open-Closed Principle)와 다형성을 실현하는 데 큰 도움이 됩니다. OCP 원칙은 소프트웨어 엔티티는 확장에 열려 있어야 하고, 수정에 닫혀 있어야 한다는 원칙으로, 인터페이스를 사용하여 이를 달성할 수 있습니다.

이상으로 Service Interface와 Service Implement를 사용하는 이유와 그 이점에 대해 알아보았습니다. 이러한 설계 방식이 왜 필요하며, 어떤 장점을 가지는지 이해하고, 코드의 유연성과 유지보수성을 높이는데 도움이 되길 바랍니다.

반응형