새소식

Java

[Java] Java 에서 인터페이스는 왜 쓰는 것일까

  • -
728x90
인터페이스를 사용하는 이유는 다양하다. 그치만 내가 어떻게 답변할 수 있을 것인가

 

다른 언어에서도 인터페이스를 사용해본 경험이 있지만Java 를 공부하는데 있어서 어떤 방식으로 실행되고 그에 사용되는 것은 무엇인지,  그 기반에 대해 알아야 더욱 이해에 대한 깊이를 더 할 수 있을 것 같다는 생각이 들었다. 하나하나 알아보자.

 

🟢 인터페이스(interface)

자식 클래스가 여러 부모 클래스를 상속받을 수 있다면, 다양한 동작을 수행할 수 있다는 장점을 가지게 될 것이다. 하지만 클래스를 이용하여 다중 상속을 할 경우, 메소드 출처의 모호성 등 여러가지 문제가 발생할 수 있어 자바에서는 클래스를 통한 다중 상속은 지원하지 않는다.

 

하지만 다중 상속의 이점을 버릴 수는 없기에 자바에서는 인터페이스라는 것을 통해 다중 상속을 지원하고 있다.

인터페이스(interface)란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미한다. java 의 핵심 개념 중 하나로 추상화, 다향성 및 다중 상속을 지원하며 implements 키워드를 사용하여 Java 클래스는 인터페이스를 구현할 수 있다.

 

자바에서 추상 클래스는 추상 메소드 뿐만 아니라 생성자, 필드, 일반, 메소드도 포함할 수 잇다. 하지만 인터페이스는 오로지 추상 메소드와 상수만을 포함할 수 있다. 기능을 정의하지만 구현을 제공하진 않는다.

 

인터페이스 예시 코드

interface Player {
    void play();
    void score();

    // Java 8 이후에 추가된 디폴트 메소드
    default void showStats() {
        System.out.println("Showing player stats...");
    }
}

 

1. 인터페이스 'Player'

 

인터페이스 정의

Player 인터페이스는 스포츠 플레이어가 가져야 할 기본적인 행동을 정의한다. 이 인터페이스를 구현하는 모든 클래스는 play()와 score() 메소드를 구현해야 한다.


디폴트 메소드 (showStats)

showStats 메소드는 디폴트 메소드로, 구현을 포함한다. 이 메소드는 인터페이스를 구현하는 모든 클래스에 자동으로 상속되며, 필요에 따라 오버라이드할 수 있다.

 

class SoccerPlayer implements Player {
    public void play() {
        System.out.println("Playing soccer...");
    }

    public void score() {
        System.out.println("Soccer player scores!");
    }
}

class BasketballPlayer implements Player {
    public void play() {
        System.out.println("Playing basketball...");
    }

    public void score() {
        System.out.println("Basketball player scores!");
    }
}

 

2. 구현 클래스 ' SoccerPlayer' 와 'BasketBallPlayer'

 

인터페이스 구현

SoccerPlayer와 BasketballPlayer 클래스는 Player 인터페이스를 구현한다. 이를 통해 각 클래스는 play(), score(), 그리고 showStats() 메소드를 갖게 된다.


메소드 구현

각 스포츠의 특성에 맞게 play()와 score() 메소드를 구현한다. 이렇게 함으로써 다양한 스포츠 플레이어가 동일한 인터페이스를 공유하면서도 각기 다른 행동을 정의할 수 있다.

 

💡인터페이스가 필요한 이유

 

완전한 추상화

- 추상화는 객체지향 프로그래밍 기술의 핵심 개념이다. 인터페이스는 메서드 시그니처만 저장하고 메서드 정의는 하지 않는다. 자바에서는 추상 클래스와 인터페이스를 이용하여 추상화를 구현한다.

 

루즈 커플링

- 결합은 한 객체 유형이 다른 객체와 다른 종속성을 나타낸다. 두 객체가 완전히 독립저깅고 한 객체에서 수행된 변경이 다른 객체에 영향을 미치지 않으면 두 객체는 느슨하게 결합되었다고 한다.

ex) interface -> service, class -> serviceimpl

 

다중 상속

-  자바클래스는 다중 상속을 지원하지 않지만, 인터페이스를 사용하여 다중 상속을 구현할 수 있다.

 

코드의 일반화와 모듈화

- 인터페이스를 사용하면 코드를 더 일반화하고 모듈화할 수 있다. 공통된 기능을 가진 클래스들이 동일한 인터페이스를 구현하면, 코드의 재사용성이 증가하고 유지 보수가 용이해진다.

 

유연성과 변경 용이

- 인터페이스를 사용하면 새로운 기능을 추가하거나 기존 기능을 수정할 때,  해당 인터페이스를 구현한 클래스에만 영향을 미친다. 따라서 변경이 필요한 경우, 해당 인터페이스를 구현한 클래스를 수정하거나 새로운 클래스를 추가하면 되므로 다른 부분에 영향을 덜 주면서 유연성을 제공한다.

 

🟢 추상화 (Abstraction)

추상화는 객체지향 프로그래밍의 4대 원칙 중 하나로, 객체의 공통적인 특성을 추출하여 인터페이스나 추상 클래스로 정의하는 것을 말한다. 이는 사용자가 중요한 부분에만 집중할 수 있도록 하여 복잡성을 줄이고 코드를 더 이해하기 쉽게 만든다. 더불어 추상화는 상속, 다형성, 캡슐화와 함께 사용됨으로써 객체지향 프로그래밍의 핵심인 객체의 코드 유지보수성과 가독성을 제공할 수 있다.

즉, 복잡한 시스템에서 중요한 부분만을 간추려 나타내는 과정이다. 이는 클래스의 구현 세부 사항을 숨기고 사용자에게 필요한 인터페이스만을 제공하여, 복잡도를 관리하고 사용자가 간단하게 객체를 사용할 수 있도록 한다.

 

abstract class Animal {
    // 모든 동물이 공유하는 메소드
    public void breathe() {
        System.out.println("Breathing...");
    }

    // 추상 메소드
    public abstract void makeSound();
}

 

1. 추상 클래스 'Animal'

 

추상 클래스 정의

Animal 클래스는 abstract 키워드로 선언되어 있으므로 인스턴스화할 수 없다. 이 클래스의 주 목적은 다른 클래스가 상속받아 사용하도록 하는 것이다.


공통 메소드 (breathe)

이 메소드는 구현이 완료되어 있으며, 모든 동물이 공통적으로 수행하는 행위인 호흡을 나타낸다. Animal 클래스를 상속받는 모든 서브 클래스는 이 메소드를 상속받아 동일하게 사용할 수 있다.


추상 메소드 (makeSound)

이 메소드는 구현이 없으며, 서브 클래스에서 반드시 구현해야 한다. 이는 각 동물마다 내는 소리가 다르기 때문에, 구체적인 동물 클래스에서 어떻게 구현될지를 정의하도록 한다.

 

class Dog extends Animal {
    // 추상 클래스에서 선언된 추상 메소드를 구현
    public void makeSound() {
        System.out.println("Bark!");
    }
}

class Cat extends Animal {
    // 추상 클래스에서 선언된 추상 메소드를 구현
    public void makeSound() {
        System.out.println("Meow!");
    }
}


2. 구현 클래스 Dog와 Cat

 

클래스 상속

Dog와 Cat 클래스는 Animal 클래스를 상속받는다. 이를 통해 breathe() 메소드와 makeSound() 메소드의 구조를 상속받게 된다.


추상 메소드 구현

Dog 클래스는 makeSound()를 "Bark!" 출력으로, Cat 클래스는 "Meow!" 출력으로 구현한다. 이는 각 동물의 특징적인 소리를 나타내며, 추상 메소드 구현을 통해 각 클래스의 독특한 행동을 정의한다.

💡추상화와 인터페이스는 무엇이 다른가

 

🔻상속 vs 구현

추상 클래스는 '상속'되어 사용되며, 추상 클래스로부터 상속받은 클래스는 추상 클래스의 모든 특성을 '상속' 받는다. 반면, 인터페이스는 '구현'된다. 클래스는 하나 이상의 인터페이스를 구현할 수 있으며, 인터페이스의 메소드를 반드시 구현해야 한다.

 

🔻구현 메소드

추상 클래스는 구현된 메소드를 포함할 수 있지만, 전통적인 인터페이스는 구현된 메소드를 포함할 수 없다.(Java8 이후로 디폴트 메소드와 정적 메소드는 가능).

 

🔻 디자인 목적

추상화는 상속을 통해 코드 재사용과 확장을 촉진하는데 중점을 둔다. 인터페이스는 다형성과 결합도를 낮추는 것에 초점을 맞추고, 여러 구현체가 동일한 계약을 따르도록 한다.

 

⭐추상 클래스와 인터페이스는 어떠한 상황에 사용해야 할까 ? 

추상 클래스

추상 클래스는 공통된 기능이 많고 일부 기능만 서브 클래스에서 다르게 구현할 때 사용한다. 예를 들어, "동물" 이라는 추상 클래스에서  "음식 먹기" , "숨 쉬기", 같은 공통 메서드를 구현하고, "소리 내기" 라는 메소드를 각 동물 종류에 따라 다르게 구현하도록 할 수 있다.

 

인터페이스

인터페이스는 다중 구현이 필요하거나, 서로 관련이 없는 클래스들이 같은 동작을 수행햐아 할 때 사용된다. 예를 들어 "Printable" 이라는 인터페이스를 정의하고, 다양한 클래스(문서, 이미지, 그래프 등) 에서 해당 인터페이스를 구현하여 "프린트" 기능을 제공할 수 있다.

 

💡Java 8부터는 인터페이스에 디폴트 메소드와 정적 메소드를 추가할 수 있게 되었다. 이 변경의 주된 이유는 인터페이스를 수정할 때 "이전 버전과의 호환성"을 유지하기 위해서다. 기존에 인터페이스를 구현한 클래스들에게 추가적인 메소드 구현 부담을 주지 않으면서도 새로운 기능을 인터페이스에 추가할 수 있게 되었다.

 

public interface MathOperations {
    // 정적 메소드를 통한 최대 공약수 계산
    static int gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }

    // 정적 메소드를 통한 최소 공배수 계산
    static int lcm(int a, int b) {
        return (a / gcd(a, b)) * b;
    }
}

 

이 변경으로 인해 개발자는 인터페이스를 더 유연하게 활용할 수 있게 되었다. 인터페이스에 새로운 메소드를 추가하더라도 기존 구현체들을 변경하지 않고도 해당 인터페이스를 사용할 수 있다. 또한 정적 메소드를 통해 유틸리티 함수를 인터페이스에 포함시킬 수 있다.

 

추상 클래스와 인터페이스를 함께 사용할 때의 장점과 주의할 점

추상 클래스와 인터페이스를 함께 사용하면, 구현의 유연성과 다형성을 동시에 확보할 수 있다. 추상 클래스로 공통 로직을 제공하고, 인터페이스를 통해 다양한 클래스가 동일한 계약을 준수하도록 만들 수 있다. 하지만 이 두가지를 함께 사용할 때는 구조가 복잡해질 수 있으므로, 설계를 명확히 하고 각각의 역할을 철저히 분리하는 것이 중요한다. 추상 클래스는 주로 상속을 통한 코드 재사용에 초점을 맞추고, 인터페이스는 다양한 클래스에 동일한 기능을 제공하는 데 초점을 맞춰야 한다.

728x90
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.