본문 바로가기
Java

Interface

by 스르나 2020. 9. 21.

Interface(인터페이스)

 

  1. 인터페이스란?
  2. 인터페이스 작성 방법
  3. 디폴트 메소드와 static 메소드
  4. 인터페이스 구현
  5. 인터페이스의 본질(의존성 분리)

 

 

1. 인터페이스란?

인터페이스는 자바의 추상 클래스의 일부이다. 인터페이스는 추상 클래스(abstract)와 비슷하지만 차이점이 있다. 인터페이스는 abstract와는 달리 몸통을 가진 메소드와 멤버 변수를 가질 수 없다. 이런 인터페이스는 간단하게 말해서 설계도로 설명할 수 있다. 또한 인터페이스는 메소드의 몸통이 없이 선언부만 가지고 있기 때문에 혼자서는 기능을 할 수 없다. 인터페이스가 기능을 가지기 위해서는 이 인터페이스를 구현한 구현체가 필요한다. 구현체는 인터페이스의 모든 메소드를 구현해야 한다.

 

2. 인터페이스 작성 방법

인터페이스작성 방법은 간단하다. 기존의 class대신 interface라는 키워드를 사용하면된다. 그런데 위에서 말한것처럼 내부의 메소드에는 메소드 선언부만 사용하고 몸통은 작성하지 않는다. 메소드의 몸통은 이 인터페이스를 구현한 구현체에서 작성한다.

 

public interface MyInferface {

    void foo();
}

위의 코드가 간단한 인터페이스를 작성한 것이다. 여기서 한가지 볼것이 있는데.

void foo();부분을 보면 접근제한자가 없다. 사실 인터페이스의 모든 메소드는 접근제한자로 public을 사용하고 추상 메도드이기 때문에 abstract를 붙여야 한다.. 하지만 모든 메소드가 public,abstract를 사용하기 때문에 생략할 수 있도록 바뀌었다.

 

위의 코드는 원래 아래와 같다.

public interface MyInferface {

    public abstract void foo();
}

 

3. 디폴트 메소드와 static 메소드

jdk1.8버전 부터 디폴트,static메소드가 추가 되었다. 이 2개가 무엇인가하면 우선 인터페이스의 단점을 좀 생각해 볼 필요가 있다.

 

인터페이스는 일종의 설계도라고도 하였다. 이런 설계도를 바탕으로 실제 기능을 가지고 있는 클래스를 구현을 한다. 하지만 설계도는 언제든지 수정이 일어날 수 있다. 그런데 여기서 한가지 문제가 생긴다. 만약 A인터페이스를 구현한 클래스가 10가지 정도라고 해보자. 이때 인터페이스에 새로운 기능이 추가된다면 어떨까? 심지어 이 기능에 10개중 5개만 사용된다고 생각해보자. 그러면 10개를 모두 수정해야 하며 새로운 기능이 필요없는 나머지 5개 클래스는 필요없는 메소드를 가지고 있는 상황이 생긴다.

 

이럴때를 대비해 새로 추가된 기능이 디폴트 메소드이다. 이 디폴트 메소드는 인터페이스에서 몸통부분까지 구현을 하는 메소드이며 인터페이스를 구현한 구현체에서는 이 디폴트 메소드가 필요하다면 오버라이딩을 통해 자신의 것처럼 사용할 수 있고, 필요없는 클래스에서는 구현하지 않고 그냥 인터페이스의 디폴트메소드로 남겨 놓으면 그만이다.

 

디폴트메소드를 만드는 것은 쉽다. 선언부 앞에 default를 붙여주기만 하면 끝이다.

 

public interface MyInferface {

    void foo();

    default void foo2(){
        System.out.println("디폴트 메소드 지롱~");
    }
}

 

public class MyClass implements MyInferface{

    public void foo(){
        System.out.println("인터페이스를 구현한 메소드 호출");
    }
    
}

 

public class Main {
    public static void main(String[] args) {
        MyClass myClass =new MyClass();
        myClass.foo2();
    }
}

main클래스를 실행하면 디폴트 메소드 지롱~라는 문구가 나온다. 보면 MyClass에는 foo2메소드가 없지만 인터페이스의 foo2메소드를 호출 하게된다. 만약 MyClass에서 foo2를 다르게 구현하고 싶다면 오버리이딩을 하면된다.

 

스태틱 메소드또한 위와같은 맥락에서 같이 사용되는데 알다싶이 static메소드는 선언을하면 빌드될때 메모리에 올라가 인스턴스를 만들지 않고 사용되는 용도이기 때문에 인터페이스의 이름으로만 사용할 수 있다.(구현체없이도 사용가능하다)

 

public interface MyInferface {

    void foo();

    default void foo2(){
        System.out.println("디폴트 메소드 지롱~");
    }

    static void foo3(){
        System.out.println("스태틱 메소드 지롱~");
    }
}

 

public class Main {
    public static void main(String[] args) {
        MyClass myClass =new MyClass();
        myClass.foo2();

        MyInferface.foo3();
    }
}

 

 

여기서 또한가지로 디폴트 메소드와 static메소드 모두 접근제한자는 public인데 숨겨져 있을 뿐이다.

4. 인터페이스 구현

위에서 작성한 인터페이스를 구현을 해보자. 인터페이스는 추상 클래스와 마찬가지로 상속을 받아서 구현을 하는데 이때 추상 클래스와 다른 차이점은 extends를 사용하지 않고 implements를 사용한다.

 

public class MyClass implements MyInferface{

    public void foo(){
        System.out.println("인터페이스를 구현한 메소드 호출");
    }
}

위 코드와 같이 foo메소드를 직접 구현해서 사용하는 것이 인터페이스를 사용한 방법의 전부라고 할 수 있다.

 

인터페이스의 특징이 한가지 있는데 자바에서 유일하게 다중 상속이 가능하다는 것이다.

 

인터페이스를 구현하는 구현체에서 한가지 인터페이스말고도 다른 인터페이스를 구현할 수 있다.

기존의 자바에서 클래스의 다중상속을 혀용하지 않은 이유는 코드의 복잡도가 높아 지기때문이 였다. 그러나 이게 단점이 될 수도 있어 인터페이스를 통한 다중상속을 가능하게 했는데 실제로 자주 사용하지는 않는다.

(객체지향에서 상속은 아주 중요한 개념이면서도 아주 어려운 부분이기도하다. 그래서 제대로된 설계가 없다면 상속 체계를 만드는 것을 조심해야 한다. 이런 상황에서 다중상속은 난이도를 더욱 높이기만 한다고 생각한다.)

 

5. 인터페이스의 본질(의존성 분리)

여기까지 봤다면 인터페이스의 사용방법을 알았을 것이고, 설계도란것도 알았을 것이다.

 

하지만 인터페이스의 본질은 의존분리에 있다. 먼저 의존이란 무엇인가하면 한 클래스에서 다른 클래스를 직접 사용하는 것이다. 이해가 안된다면 한번 예시를 들어보자.

 

자동차 클래스를 만드는데 자동차에 들어가는 모터의 클래스를 따로 만들어서 사용한다고 생각해보자.

package main.example;

public class Motor {
    
    public void foo(){
        
    }
    
    public void foo2(){
        
    }
    
    public void foo3(){
        
    }
}

위는 모터 클래스이다.

 

package main.example;

public class Car {
    private Motor motor;

    public Car(){
        this.motor = new Motor();
    }

    public void startMotor(){
        this.motor.foo();
    }

    public void go(){
        this.motor.foo2();
        this.motor.foo3();
    }
}

자동차 클래스에서의 startMotor와 go 메소드는 모터의 foo1,2,3기능이 필요하다. 그리고 모터 클래스를 생성자에서 직접 인스턴스를 만들어서 사용하고있다.

 

그런데 여기서 만약 모터를 다른 것으로 대체한다면 어떻게 될까?(예를들어 기존의 모터에 문제가 생겨 foo1,2,3기능은 그대로 필요하지만 모터클래스를 더 이상 사용하기 힘든 경우) 자동차 클래스는 모터를 이용한 부분 모두를 수정해야 한다. 이렇게 한 클래스가 다른 클래스를 직접 사용하는 것을 의존성이 강하다고 한다.

 

이런경우는 객체지향에서 굉장히 안좋은 경우이다. 이런 것을 막는 역할이 바로 인터페이스이다.

처음부터 startMotor와 go에들어갈 기능(foo1,2,3)을 설계해서 인터페이스를 만들었다면 이 기능을 수행할 클래스를 찾아서 갈아끼우기만 하면된다.

 

아래의 예를 보자

 

package main.example;

public interface MotorInterface {
    
    void foo();
    void foo2();
    void foo3();
}

모터의 기능을 설계한 인터페이스이다.

 

package main.example;

public class Car {
    private MotorInterface motorInterface;

    public Car(MotorInterface motorInterface){
        this.motorInterface = motorInterface;
    }

    public void startMotor(){
        this.motorInterface.foo();
    }

    public void go(){
        this.motorInterface.foo2();
        this.motorInterface.foo3();
    }
}

여기에서는 모터를 직접 사용하지 않고 다른 모터인터페이스를 구현한 구현체를 생성자를 통해 받아서 사용한다.

 

이렇게하면 모터가 바뀔때 생성자에 모터인터페이스를 구현한 모터를 넣어주면 기존의 자동차클래스의 수정이 필요 없다. 이런 경우가 의존이 약하게 되어있는 경우이고 객체지향에서 지향하는 것이다.

 

이렇게 인터페이스는 주로 객체간의 의존성을 분리하는 것이 주요역할이다. 앞으로 객체간의 관계를 설정할때는 직접적으로 서로 참조하는 것이 아니라. 인터페이스를 통해 참조하게끔 하는 것이 올바른 자바의 모습이다.

'Java' 카테고리의 다른 글

Thread-2  (0) 2021.02.11
Thread-1  (0) 2021.02.08
Generic  (0) 2020.07.21
Stream  (0) 2020.07.15
lambda2  (0) 2020.07.10