3. 기초_인터페이스

  • 인터페이스는 특정 클래스가 구현해야 하는 메서드의 집합입니다.

  • 메서드의 선언만 포함하고, 실제 구현은 포함하지 않습니다.

  • 여러 클래스 간의 공통된 행동을 정의합니다.

  • 다형성을 구현하기 위해서 사용됩니다.

인터페이스의 특징

  • 인터페이스의 메서드는 public 과 abstract 로 간주됩니다.

  • Java 8이후 부터는 default 와 static 메서드를 사용하여, 인터페이스 내에서 구현된 메서드를 포함 할 수 있습니다.

  • default 메서드는 인터페이스 구현체가 별도로 구현하지 않아도 되는 기본 메서드를 제공합니다.

  • static 메서드는 인터페이스 자체에 속합니다.

  • 내부의 모든 필드는 public static final 로, 오로지 상수만 정의할 수 있습니다.

  • 다중 구현(여러 인터페이스를 구현)하여 다중 상속의 이점을 활용할 수 있습니다.

  • 인터페이스를 통해 상위 클래스가 하위 클래스에 의존하지 않고, 인터페이스에 의존하도록 설계할 수 있어 시스템의 유연성과 확장성을 높입니다.

인터페이스와 추상 클래스의 기능적 차이점

  • 인터페이스

    • 다중 구현 가능

    • 필드

      • 모든 필드는 public static final

      • 따라서, 인터페이스로 모든 중복된 필드를 통합할 수는 없습니다.

    • 메서드: Java 8이후 부터는 default, static 구현 가능. 모든 메서드는 기본적으로 public 이며 접근 제어자 사용 할 수 없음

  • 추상 클래스

    • 단일 상속만 가능

    • 필드: 필드와 생성자 모두 가질 수 있음. 모든 접근 제한자 사용 가능.

    • 메서드: 추상 메서드 일반 메서드 포함 가능. 모든 메서드와 필드에 다양한 접근 제어자 사용 가능

인터페이스와 추상 클래스의 차이점

인터페이스는 부모 자식 관계인 상속에 얽메이지 않고, 해당 공통 기능이 필요한 곳에 구현하는 식으로, 추상 클래스보다 자유롭게 붙였다 뗐다 사용할 수 있습니다. 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용하는 것에 초점을 맞춥니다. 다중 구현이 된다는 점을 이용해, 내부 멤버가 없는 빈 껍데기 인터페이스를 선언하여, 마커 인터페이스로서 이용이 가능합니다.

마커 인터페이스

  • 메서드나 필드가 없이 단순히 특정 클래스에 대한 정보를 제공하는 용도로 사용됩니다.

  • 예를들어 Serializable 인터페이스는 객체가 직렬화 가능하다는 것을 나타냅니다.

인터페이스 네이밍 규칙 중 하나는 -able 접미사를 사용하는 것입니다. 이는 인터페이스가 특정 기능이나 속성을 가진 객체를 정의할 때 사용됩니다. (Readable, Writable 등..)

인터페이스가 구현 객체의 같은 동작을 보장하도록 사용된다면, 추상 클래스는 클래스간의 연관 관계에 초점을 맞춥니다.

즉, 인터페이스는 '구현' 메서드를 각 클래스의 목적에 맞도록 기능을 구현하는 느낌이라면, 추상 클래스는 extends 키워드를 사용해서 자신의 기능들을 하위 클래스로 확장시키는 느낌입니다.

추상클래스를 사용하는 경우

  • 상속받을 클래스들이 공통으로 가지는 메소드와 필드들이 많아 통합할 때

  • 필드에 public 이외의 접근자(protected, private) 선언이 필요한 경우

  • non-static, non-final 필드 선언이 필요한 경우 (각 인스턴스에서 상태 변경을 위한 메서드가 필요한 경우)

  • 하위 클래스가 오버라이드하여 재정의하는 기능들을 공유하기 위한 상속 개념을 사용할 때

  • 미리 논리적인 클래스 상속 구조를 만들어 놓고 사용

  • 클래스 끼리 명확한 계층 구조화

인터페이스를 사용하는 경우

  • 기능에 대해서 정의해야 하지만, 그 구현 방식과 대상에 대해서 추상화 해야 할 때. 즉, 각 구현체에서 방식이 달라지는 경우. 그러나 본질적으로는 같은 의미의 동작

  • 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해

  • 서로 관련성이 없는 클래스들을 묶어주고 싶을 때(형제?)

  • 다중 상속(구현)을 통한 추상화 설계를 해야 할 때

다음의 코드는 모든 자식 클래스에서 부모 클래스의 추상 클래스를 재정의해야 하는 코드이다.

// 추상 클래스 (조상 클래스)
abstract class Creature {
  abstract void swimming(); // 수영 동작을 하는 추상 메소드
}

// 추상 클래스 (부모 클래스)
abstract class Animal extends Creature { }
abstract class Fish extends Creature { }

// 자식 클래스
class Parrot extends Animal {
  void swimming() {} // 앵무새는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
}

class Tiger extends Animal {
  void swimming() {} // 호랑이는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
}

class People extends Animal {
  void swimming() {
    // ...
  }
}

class Whale extends Fish {
  void swimming() {
      // ...
    }
}

다음은 interface 를 사용하여 swimming 기능이 필요한 클래스에만 구현하도록 강제하는 경우이다.

abstract class Creature { }

abstract class Animal extends Creature { }
abstract class Fish extends Creature { }

// 수영 동작 추상 메소드를 따로 인터페이스를 만들어 넣는다.
interface Swimmable {
    void swimming();
}

class Tiger extends Animal { }
class Parrot extends Animal { }
class People extends Animal implements Swimmable{ // 인터페이스를 구현함으로써 동작이 필요한 클래스에만 따로 상속에 구애받지않고 묶음
    @Override
    public void swimming() {}
}

class Whale extends Fish implements Swimmable{ // 인터페이스를 구현함으로써 동작이 필요한 클래스에만 따로 상속에 구애받지않고 묶음
    @Override
    public void swimming() {}
}

마커 인터페이스

  • 아무 내용이 없는 빈 껍데기 인터페이스를 선언하고 적절한 클래스에 구현하여, 추상화/다형성 이런걸 떠나서 그냥 단순한 타입 체크용으로 사용하는 것이다. 이러면 조건문 코드도 심플해진다.

// 새끼를 낮을 수 있다는 표식 역할을 해주는 마커 인터페이스
interface Breedable {}

class Animal {
    public static void born(Animal a) {
      //  if(a instanceof Lion) {
      //       System.out.println("새끼를 낳았습니다.");
      //   } else if(a instanceof Chicken) {
      //       System.out.println("알을 낳았습니다.");
      //   } else if(a instanceof Snake) {
      //       System.out.println("알을 낳았습니다.");
      //   }
        if(a instanceof Breedable) {
            System.out.println("새끼를 낳았습니다.");
        } else {
            System.out.println("알을 낳았습니다.");
        }
    }
}

class Lion extends Animal implements Breedable { }
class Chicken extends Animal { }
class Snake extends Animal { }

그래서 인터페이스란

프로그램을 설계하고 좀 더 유연하게 만드는 기법을 말한다.

  • 역할1: 내부 구성에 대한 학습 없이, 그저 지원해주는 메서드를 이용하여 간편하게 프로젝트를 개발 할 수 있게 해주는 것

  • 역할2: 일종의 '스펙'을 지정하여, 소프트웨어 확장에 이점을 취함

    • 실생활의 예를 들면, 어떤 키보드를 사용하든지 제조사에 상관없이 컴퓨터에 연결만하면 설치없이 바로 이용할 수 있는 것이며, 이러한 간편함은 OS에서 미리 I/O 처리에 대해 인터페이스로 추상화하였기 때문

  • 객체지향 프로그래밍 전략에서 결합도를 낮추고 응집도를 높여 유지보수성을 향상시키는 디자인 패턴으로써의 역할도 병행합니다.

  • public static final 과 public abstract 제어자는 생략이 가능하다. 인터페이스에 정의된 모든 멤버에 적용되는 사항이기 때문에 편의상 생략 가능하게 지원하는 것이다. 생략된 제어자는 컴파일 시에 컴파일러가 자동으로 추가해 준다.

  • 만일, 클래스가 구현하는 인터페이스의 메서드 중 일부만 구현해야 한다면, 추상클래스로 선언해야 한다. 어찌보면 당연하지만 인터페이스의 추상 메서드 멤버를 그대로 상속받기 때문에, 인터페이스를 상속한 클래스 에서 메서드 구현을 안한다면, 곧 추상 메서드를 가진 추상 클래스가 되기 때문이다.

  • 인터페이스끼리 상속을 할 때는 extends로 확장한다.

  • 인터페이스는 하나의 타입이나 규격일 뿐이지, 그 자체가 하나의 객체가 되는 것은 아닙니다. 인터페이스의 상속은 부모의 속성과 동작을 물려받는 것이 아니라, 정확히 말하면, 인터페이스의 상속은 규격이나 스펙 자체 혹은 자체의 선언을 물려받는 것입니다. 규격이나 스펙을 물려받아서 새로운 스펙을 만든다면 기존 여러 개의 스펙을 조합해서 하나로 묶거나 기존의 스펙을 고스란히 물려받은 후에 다시 추가적인 기능을 가지게 하는 것입니다.

interface Changeable{
    /* 채널을 바꾸는 기능의 메서드 */
    void change();
}

interface Powerable{
    /* 전원을 껐다 켰다 하는 메서드 */
    void power(boolean b);
}

// 채널 기능과 전원 기능을 가진 인터페이스들을 하나의 인터페이스로 통합 상속
interface Controlable extends Changeable, Powerable {
  // 인터페이스끼리 다중 상속하면 그대로 추상 멤버들을 물려 받음
}

// 클래스에 통합된 인터페이스를 그대로 상속
class MyObject implements Controlable {
  public void change() {
    System.out.println("채널을 바꾸는 기능의 메서드");
  }
  public void power(boolean b) {
    System.out.println("전원을 껐다 켰다 하는 메서드");
  }
}

Last updated