
API ( Application Programming Interface ) 에서 I 는 인터페이스를 의미하는데, 인터페이스 기반의 개발이 기민성을 제공하는데 왜 그럴까?
그전에, 객체 지향 설계 SOLID 의 ISP에 대해 짚고 넘어가겠다.
ISP는 인터페이스를 작은 단위로 분리하는 것 이다.
이걸 넓게 보면 객체 지향 프로그래밍에서 인터페이스 기반 개발(Interface-based Development) 자체가 소프트웨어의 기민성을 크게 향상시키는 중요한 요소이다.
구현과 분리: 인터페이스는 "무엇을 할 것인가"를 정의하고, "어떻게 할 것인가"는 구현체(클래스)에게 맡긴다. 즉, 구현체와 사용하는 클라이언트 코드 사이에 추상적인 인터페이스라는 '벽'을 세운다.
교체 용이성: 클라이언트 코드는 특정 구현체에 직접 의존하는 대신, 인터페이스에만 의존한다. 이렇게 되면 인터페이스를 구현하는 어떤 객체로도 쉽게 교체할 수 있다. 예를 들어, 데이터 저장 방식이 파일에서 데이터베이스로 바뀌더라도, 클라이언트 코드는 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. 가독성 및 유지보수성 향상
역할 명확화: 인터페이스는 해당 클래스가 어떤 역할을 수행하는지 명확하게 정의해준다. 이는 코드의 가독성을 높이고, 다른 개발자가 코드를 이해하고 유지보수하기 쉽게 만든다.
기민성과의 연결: 코드를 이해하고 변경하는 데 드는 비용이 줄어들므로, 장기적으로 시스템의 유지보수 비용을 절감하고 변화에 유연하게 대응할 수 있게 한다.