인터페이스 기반의 개발이 기민성을 제공하는가?

지꾸이·2025년 7월 8일

Spring

목록 보기
6/6

API ( Application Programming Interface ) 에서 I 는 인터페이스를 의미하는데, 인터페이스 기반의 개발이 기민성을 제공하는데 왜 그럴까?

그전에, 객체 지향 설계 SOLID 의 ISP에 대해 짚고 넘어가겠다.

ISP는 인터페이스를 작은 단위로 분리하는 것 이다.

이걸 넓게 보면 객체 지향 프로그래밍에서 인터페이스 기반 개발(Interface-based Development) 자체가 소프트웨어의 기민성을 크게 향상시키는 중요한 요소이다.

  1. 낮은 결합도 ( Loose Coupling)
  • 구현과 분리: 인터페이스는 "무엇을 할 것인가"를 정의하고, "어떻게 할 것인가"는 구현체(클래스)에게 맡긴다. 즉, 구현체와 사용하는 클라이언트 코드 사이에 추상적인 인터페이스라는 '벽'을 세운다.

  • 교체 용이성: 클라이언트 코드는 특정 구현체에 직접 의존하는 대신, 인터페이스에만 의존한다. 이렇게 되면 인터페이스를 구현하는 어떤 객체로도 쉽게 교체할 수 있다. 예를 들어, 데이터 저장 방식이 파일에서 데이터베이스로 바뀌더라도, 클라이언트 코드는 DataStorage 인터페이스만 사용한다면 내부 구현체가 바뀌어도 클라이언트 코드를 수정할 필요가 없다.

// 인터페이스
interface DataStorage {
    void save(String data);
}

// 파일 저장 구현체
class FileStorage implements DataStorage {
    public void save(String data) {
        System.out.println("파일에 저장: " + data);
    }
}

// DB 저장 구현체
class DatabaseStorage implements DataStorage {
    public void save(String data) {
        System.out.println("데이터베이스에 저장: " + data);
    }
}

// 클라이언트 코드
class Client {
    private final DataStorage storage;

    public Client(DataStorage storage) {
        this.storage = storage;
    }

    public void process(String data) {
        storage.save(data);
    }
}

// 실행
public class Main {
    public static void main(String[] args) {
        Client client1 = new Client(new FileStorage());
        client1.process("Hello, File!");

        Client client2 = new Client(new DatabaseStorage());
        client2.process("Hello, DB!");
    }
}

이 구조 덕분에 Client는 FileStorage든 DatabaseStorage든 상관없이 인터페이스만 알면 되니까 구현체 교체가 매우 쉽다.

  • 기민성과의 연결: 소프트웨어의 한 부분이 변경되더라도 다른 부분에 미치는 영향을 최소화한다. 이는 변화하는 요구사항에 빠르게 대응하고, 새로운 기능을 추가하거나 기존 기능을 수정할 때 예상치 못한 부작용을 줄여준다. 즉, 코드가 덜 깨지므로 더 빠르게 변경하고 배포할 수 있게 된다.

2. 다형성 (Polymorphism)의 활용

  • 다양한 구현체 수용: 인터페이스를 사용하면 하나의 인터페이스 타입으로 여러 다른 구현체 객체를 참조할 수 있다. 이는 다형성의 핵심.

  • 기민성과의 연결: 시스템이 다양한 시나리오나 확장 기능을 유연하게 수용할 수 있도록 설계되어, 새로운 비즈니스 요구사항이나 기술 변화에 훨씬 더 민첩하게 대응할 수 있게 된다.

public interface Animal {
    void makeSound(); // 동물이 소리를 내는 추상 메서드
}

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍!");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("야옹!");
    }
}

public class Cow implements Animal {
    @Override
    public void makeSound() {
        System.out.println("음메~");
    }
}

Animal myDog = new Dog();
Animal myCat = new Cat();
Animal myCow = new Cow();

3. 병렬 개발 (Parallel Development) 촉진

  • 독립적인 작업: 인터페이스는 계약과 같아서, 팀원들이 각자의 구현체를 독립적으로 개발할 수 있게 한다. 한 팀원은 인터페이스를 사용하는 클라이언트 코드를 개발하고, 다른 팀원은 그 인터페이스를 구현하는 코드를 동시에 개발할 수 있다.

  • 통합 용이성: 인터페이스는 각 모듈 간의 '약속' 역할을 하므로, 개발이 완료된 후에도 각 모듈을 통합하기가 훨씬 용이하다.

  • 기민성과의 연결: 여러 개발자가 동시에 다른 부분을 작업할 수 있으므로, 전체 개발 일정을 단축시키고, 빠르게 기능을 완성하여 시장에 내놓는 애자일 개발의 목표에 부합한다.

4. 테스트 용이성 (Testability)

  • 쉬운 목(Mock) 객체 생성: 인터페이스는 테스트 환경에서 실제 구현체 대신 목(Mock) 객체나 스텁(Stub) 객체를 쉽게 만들 수 있게 한다. 실제 데이터베이스 연결이나 외부 API 호출 없이도 단위 테스트를 수행할 수 있다.

  • 격리된 테스트: 각 모듈이 인터페이스를 통해 의존하기 때문에, 특정 모듈만 독립적으로 테스트하기 용이하다.

  • 기민성과의 연결: 테스트가 쉽고 빠르다는 것은 버그를 조기에 발견하고 수정하는 데 도움이 된다. 이는 개발 속도를 저해하지 않으면서 코드 품질을 높이고, 변경 사항에 대한 확신을 가지고 빠르게 배포할 수 있게 해 기민성을 향상시킨다.

5. 가독성 및 유지보수성 향상

  • 역할 명확화: 인터페이스는 해당 클래스가 어떤 역할을 수행하는지 명확하게 정의해준다. 이는 코드의 가독성을 높이고, 다른 개발자가 코드를 이해하고 유지보수하기 쉽게 만든다.

  • 기민성과의 연결: 코드를 이해하고 변경하는 데 드는 비용이 줄어들므로, 장기적으로 시스템의 유지보수 비용을 절감하고 변화에 유연하게 대응할 수 있게 한다.

profile
백엔드

0개의 댓글