Java/Java Study

[객체지향] 인터페이스(Interface) (5/24 어휘수정)

모모토 2021. 5. 16. 17:55
반응형

● 인터페이스는 기존의 추상화 클래스보다 더 추상화가 높은 추상화 클래스의 개념이며, 기존의 미완성 설계도라는 추상 클래스의 개념과 거의 일치한다.

추상 클래스와 더불어 다소 모호한 개념인 인터페이스에 대해서 알아보기 전에 , 우선 인터페이스는 무엇인지 추상 클래스와 비교해서 알아보고, 인터페이스의 작성규칙과 인터페이스의 다형성까지 공부하면, 인터페이스의 장점에 대해 이해하기 쉬울 것이다. 인터페이스의 장점을 알면 이것을 왜 쓰는지, 쓰는 목적까지 공부하는 데 있어서 좀 더 수월할 것이다.

 

 

1) 추상 클래스 vs 인터페이스

 

이전에 포스팅했던 추상 클래스는 abstract가 제어자로 붙는 클래스이다(추상 메서드를 포함하는).

추상 클래스는 추상 메서드 외에도 멤버 변수와 또한 완성된 메서드를 가질 수 있다. 그에 반해 인터페이스(interface)는 Only  추상 메서드만을 갖기 때문에, 추상 메서드의 집합이라고 봐도되고, 따라서 추상화의 정도가 더 높다(static 메서드, 디폴트 메서드는 JDK 1.8부터 허용한다).

 

그 외에 차이점이라 하면, 인터페이스는 작성 시 class가 아닌 interface로 표기하며, 상속받을 땐 extends가 아닌 implements로 상속받는다.

 

많은 차이점이 존재하지만, 추상 클래스와 인터페이스 둘 다 인스턴스를 생성할 수 없으며, 클래스에 상속시켜 추상 메서드의 구현을 강요한다는 것은 똑같다.

 

 

2) 인터페이스의 작성 규칙

 

2 - a) 인터페이스는 작성 시 class가 아닌 interface를 사용하며, 상속 시 implements를 사용한다.

 

2 - b) 모든 멤버 변수는 ' public static final 타입 상수 이름 = 값; ' 형태로 선언하며, public static final은 생략 가능하다.

 

2 - c) 모든 메서드는 ' public abstract 리턴 타입 메서드 이름(); ' 형태여야 하며 , public abstract은 생략 가능하다.

(static 메서드, 디폴트 메서드는 예외, 인터페이스에서의 static메서드와 디폴드 메서드에 관한 설명은 나중에 포스팅 예정)

 

인터페이스의 작성 규칙에서 알 수 있듯, 멤버 변수와 메서드에 public이란 접근제어자가 붙고 이를 생략할 수 있다. 따라서 자손 타입에서 오버 라이딩할 때 접근제어자를 쓰지 않으면 default로 설정하기 때문에 오버 라이딩의 규칙(오버 라이딩할 때 조상 메서드의 접근제어 자보다 넓거나 같은 범위의 접근제어자를 사용해야 한다.)을 위반하기에 에러가 난다. 따라서 자손 클래스의 접근제어자를 잘 신경 써서 오버 라이딩해주어야 한다.

 

 

3) 인터페이스의 상속과 구현

 

interface MathClass { //인터페이스 생성
	void mathHomework(); // 추상메서드 생성
}

class Student1 implements MathClass { // 인터페이스 상속

	@Override
	public void mathHomework() { // 제어자를 public으로 맞춰줌
		System.out.println("수학 숙제중");// 인터페이스 메서드 구현
		
	}

}
public class Interface_practice {
	static Student1 S1 = new Student1();
	public static void main(String[] args) {
	S1.mathHomework();
	}
}

인터페이스 MathClass를 상속받고 구현한 자손 클래스의 객체를 생성한 코드

 

 

4) 인터페이스의 다형성

 

인터페이스와 이를 상속받은 클래스 간에도 다형성의 개념이 통한다. 바로 조상 타입의 참조 변수로 자손 타입 인스턴스를 참조할 수 있다는 것, 

interface MathClass {
	void mathHomework();
}

class Student1 implements MathClass {

	@Override
	public void mathHomework() {
		System.out.println("수학 숙제중");
		
	}

}
public class Interface_practice {
	static MathClass S1 = new Student1(); // 조상타입으로 자손클래스의 인스턴스를 생성
	public static void main(String[] args) {
	S1.mathHomework();
	}
}

 

실행결과는 똑같다, 따라서 인터페이스에도 다형성이 존재한다는 걸 알 수 있다.

 

인터페이스의 다형성에 대해서 좀 더 자세히 살펴보자면, 조금 특이한 부분들이 존재한다. 본인 블로그 게시물인 미니게임에서 만든 코드를 가져와서 살펴보자. 본문에선 추상 클래스였던 Fightable을 인터페이스로 바꾸었고 상속 문법만 바꾸었을 뿐 다른 곳은 건드리지 않았다.

 

interface Fightable { //인터페이스 형성
    int attack (); // Only 추상클래스 or 상수만 존재가능
    boolean guard();
}

class Warrior implements Fightable { //인터페이스를 상속받음
    int HP = 150;
    int guardProperty = 70;
    @Override
    public int attack() {
        return 10;
    }

    @Override
    public boolean guard() {

        int x = (int)(Math.random()*10)+1;

        if(x*10>guardProperty)
            return true;
         else
            return false;
    }

    public String toString(){
        return "Warrior";
    }
}

Class , Warrior(W)와 Magician(M)은 각각 Fightable을 상속받았다.

 

class Attack {
    Warrior warrior = new Warrior(); //player 1
    Magician magician = new Magician(); //plater 2
    int turn =0;

    public void attackBy(Fightable f) { // 주목해야 할 부분

       // 너무 길어서 내용은 생략

    }
}

 

우리가 주목해야 할 코드는 바로 " public void attackBy(Fightable f){ // 내용 생략} " 이 부분이다. 메서드의 매개변수 부분을 해석할 줄 알아야 한다. 괄호 안의 의미 즉, 인터페이스 타입(Fightable)의 매개변수가 갖는 의미는 메서드 attackBy는  Fightable을 구현한 메서드의 참조 변수(인스턴스)만을 매개변수로 받겠다는 의미이다.

return 타입이 Fightable이라면  마찬가지로 Fightable을 구현한 클래스의 인스턴스를 반환한다는 의미이며 인터페이스의 다형성에서 가장 중요한 개념이니 잘 알아 두어야 한다.

 

 

5) 인터페이스의 장점들

 

  • A) 개발 시간을 단축시킬 수 있다.

 인터페이스는 선언과 구현을 따로 분리하기 때문에 한쪽은 선언된 인터페이스를 이용하여 개발을 하고, 다른 개발자는 인터페이스를 구현하는 클래스를 만들 수 있으므로 개발 시간이 단축될 수 있다.

 

 

  • B) 표준화가 가능하다.

 다양한 개발자가 모여 프로젝트를 진행할 때 해당 프로젝트에 사용되는 툴을 인터페이스로 작성하여 구현부에 대한 가이드라인을 제시하여 프로그램을 작성하도록 하면 보다 더 일관되고 안정적인 프로그램의 개발이 가능해진다.

 

 

  • C) 서로 관계없는 클래스들의 관계를 맺어 줄 수 있다.

 상속 관계도 아니고, 같은 부모를 갖는 관계도 아닌 접점이 없는 클래스들에 동일한 인터페이스를 구현함으로써 특정한 관계를 맺어 줄 수 있다. 

 

 

  • D) 독립적인 프로그래밍이 가능하다.

 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있다. 예를 들어 클래스 A와 클래스 B가 직접적인 관계, A-B 같은 상태에 있다면 클래스 A를 수정할 때 클래스 B도 수정해주어야 한다. 하지만 만약 인터페이스를 사용하여 A-I-B 형태의 간접적인 형태로 만든다면 A, B는 서로 독립적이고 만약 어느 한쪽을 바꿔도 서로에게 영향을 주지 않는 상태가 된다. 이러한 대표적인 예시가 바로 JDBC이다(자세한 설명은 JDBC링크 클릭). 각 DBMS회사들이 JDBC API를 참고하여 만들기 때문에 DB회사를 바꾸어도 우리의 코드는 변하지 않아도 되는 것이다.

 

 

6) 독립적인 프로그래밍의 이해 (인터페이스의 본질)

 

독립적인 프로그래밍은 인터페이스의 본질을 잘 설명해줄 수 있다. 위에서 클래스 A, B를 직접적인 연결관계로 코드를 짜 보면 아래와 같다.

class A { // 사용자
    public void method(B b) {
        b.methodB();
    }
}

class B { // 공급자
    public void methodB(){
        System.out.println("I am Provider");
    }
}

public class InterfaceTest03 {
    static A a = new A();

    public static void main(String[] args) {
        a.method(new B());
    }
}

 클래스 A는 사용자로서 클래스 B를 사용한다. 따라서 만약 클래스 B가 아닌 다른 클래스를 사용하거나 클래스 B의 내용을 수정할 때 클래스 A도 작든 크든 어쩔 수 없는 코드의 수정이 필요하다 이때 직접적인 연결관계가 아닌 인터페이스를 이용한, A-I-B 형태의 간접적인 연결관계로 코드를 짜면 어떻게 될까?

 

class A { // 사용자
    public void methodA(I i) {
        i.method();
    }
}

interface I {
    void method();
}

class B implements I { // 공급자 1
    public void method(){
        System.out.println("I am Provider B");
    }
}

class C implements I { // 공급자 2
    public void method(){
        System.out.println("I am Provider C");
    }
}


public class InterfaceTest03 {
    static A a = new A();

    public static void main(String[] args) {
        a.methodA(new B());
        a.methodA(new C());
    }
}

클래스 A(사용자)는 interface I를 구현한 클래스의 인스턴스를 받아줌으로써 I를 구현한 클래스 C, B 중 어느 걸 선택해도 클래스 A의 코드는 바꾸어 줄 필요가 없어지게 된다. 이것이 바로 interface의 본질이라고 볼 수 있다.