Java - Interface란?

박민수·2024년 11월 20일
post-thumbnail

개요

자바 공부를 하면서 요즘 가장 많이 고민했던 주제 중 하나가 바로 인터페이스이다. 처음에는 인터페이스가 단순히 "구현해야 할 메서드 목록" 정도로만 이해되었는데, 공부를 하다 보니 이 개념이 얼마나 중요한 역할을 하는지 점점 깨닫게 되었다. 이번 글에서는 자바의 인터페이스가 무엇인지, 왜 사용하는지에 대해 정리하고자 한다.

인터페이스(Interface)란?

자바에서 인터페이스는 객체 지향 설계의 핵심 개념 중 하나로, 클래스가 반드시 구현해야 할 메서드의 집합을 정의한다. 인터페이스는 구현 코드가 없이 메서드 선언부만 제공되며, 이를 구현하는 클래스는 모든 메서드를 반드시 구현해야 한다. 인터페이스는 다형성, 결합도 감소, 코드의 유연성 향상 등 다양한 이점을 제공하며, 복잡한 시스템의 설계를 간소화하는 중요한 개념이다.

인터페이스의 기본 원리와 구조

인터페이스는 추상 클래스와 비슷하지만, 자바에서 다중 상속의 문제를 해결하는 방식으로 도입되었다. 자바에서는 클래스의 다중 상속을 지원하지 않지만, 여러 인터페이스를 구현할 수 있게 함으로써 다중 상속의 장점을 취할 수 있다. 인터페이스는 상수와 추상 메서드를 정의할 수 있으며, 자바 8부터는 디폴트 메서드와 정적 메서드를, 자바 9부터는 private 메서드도 허용되었다.

interface Animal {
    void makeSound(); // 추상 메서드
    default void breathe() { // 디폴트 메서드
        System.out.println("Breathing...");
    }
    static void info() { // 정적 메서드
        System.out.println("Animals have various sounds.");
    }
}

이렇게 인터페이스는 다양한 방식으로 확장되었고, 이를 통해 기능을 좀 더 유연하게 제공할 수 있게 되었다.

인터페이스를 사용하는 이유

1. 다형성(Polymorphism) 구현

다형성은 객체 지향 프로그래밍의 중요한 개념으로, 인터페이스는 다형성을 구현하는 데 매우 유용한 도구로 활용된다. 같은 인터페이스를 구현하는 여러 클래스들이 공통된 메서드를 제공함으로써, 코드의 일관성을 유지하고 확장성을 높인다. 예를 들어, List 인터페이스를 구현한 ArrayListLinkedList 클래스가 있다. 두 클래스는 같은 메서드를 가지고 있지만, 그 메서드가 동작하는 방식은 서로 다르다. 이러한 다형성 덕분에 하나의 메서드가 다양한 형태의 객체를 처리할 수 있다.

List<String> list = new ArrayList<>();
list.add("Hello");
list = new LinkedList<>(); // 쉽게 다른 구현체로 교체 가능

이렇게 하면 코드가 더 유연해지고, 새로운 요구사항에 따라 다른 구현체를 쉽게 도입할 수 있다.

2. 결합도(Coupling) 낮추기

결합도는 클래스 간의 의존성을 나타내는 지표로, 결합도가 낮을수록 코드의 유지보수성이 향상된다. 인터페이스는 클래스 간의 결합도를 낮추는 데 효과적이다. 이를 통해 클래스들은 구체적인 구현보다는 추상적인 인터페이스에 의존하게 된다. 예를 들어, PaymentProcessor라는 인터페이스를 사용하면, CreditCardPayment나 PayPalPayment 같은 다양한 결제 방식을 쉽게 추가하거나 변경할 수 있다.

interface PaymentProcessor {
    void processPayment(double amount);
}

class CreditCardPayment implements PaymentProcessor {
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

class PayPalPayment implements PaymentProcessor {
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of $" + amount);
    }
}

이렇게 설계하면 PaymentProcessor 인터페이스에 의존하는 클래스는 결제 방식이 추가되거나 변경될 때 수정할 필요가 없다.

3. 다중 상속 문제 해결

자바에서는 클래스의 다중 상속을 지원하지 않는다. 다중 상속은 복잡성과 충돌 문제를 야기할 수 있기 때문이다. 하지만 인터페이스를 통해 이러한 문제를 우회할 수 있다. 클래스는 여러 인터페이스를 동시에 구현할 수 있어 다양한 기능을 유연하게 제공할 수 있다. 예를 들어, Swimmable과 Runnable 인터페이스를 동시에 구현하여 수영과 달리기 기능을 모두 갖춘 클래스를 만들 수 있다.

interface Swimmable {
    void swim();
}

interface Runnable {
    void run();
}

class Amphibian implements Swimmable, Runnable {
    public void swim() {
        System.out.println("Swimming");
    }
    public void run() {
        System.out.println("Running");
    }
}

이 방식은 다중 상속의 장점을 가져오면서도 복잡성을 최소화하는 방법이다.

인터페이스의 확장: 디폴트 메서드와 정적 메서드

자바 8부터 인터페이스에 디폴트 메서드와 정적 메서드가 추가되었다. 디폴트 메서드는 인터페이스에 메서드 구현을 포함할 수 있게 함으로써, 인터페이스를 구현하는 기존 클래스들이 새로운 메서드를 강제로 구현하지 않아도 되게 한다. 이는 라이브러리 설계 시, 하위 호환성을 유지하면서 인터페이스를 확장할 수 있게 해준다.

interface Vehicle {
    void start();
    default void stop() {
        System.out.println("Vehicle stopping...");
    }
}

정적 메서드는 인터페이스 자체에서 메서드를 호출할 수 있게 해 준다. 이러한 기능은 인터페이스를 보다 유연하게 사용할 수 있게 해준다.

테스트와 인터페이스의 활용

인터페이스는 테스트 코드 작성에도 매우 유용하다. 인터페이스를 사용하면 특정 클래스의 구현에 의존하지 않고 Mock 객체를 만들어 테스트할 수 있다. 예를 들어, 데이터베이스에 의존하는 클래스가 있을 때, 테스트 환경에서는 Mock 객체를 사용해 데이터베이스 연결 없이도 테스트를 수행할 수 있다. 이는 테스트의 효율성을 높이고, 더 안정적인 코드 작성을 가능하게 한다.

interface Database {
    void connect();
}

class MockDatabase implements Database {
    public void connect() {
        System.out.println("Mock connection established.");
    }
}

결론

인터페이스는 자바에서 코드의 유연성과 확장성을 높이고, 결합도를 줄이며, 다형성을 구현하는 데 필수적인 역할을 한다. 인터페이스를 잘 활용하면 복잡한 시스템을 더 효율적이고 관리하기 쉽게 설계할 수 있다. 자바 개발자라면 인터페이스를 이해하고 적극적으로 활용하는 것이 필수적이다.

profile
안녕하세요 백엔드 개발자입니다.

0개의 댓글