자바 객체 지향 프로그래밍 - 인터페이스

계리·2023년 1월 11일
0
post-thumbnail
post-custom-banner

인터페이스란?

일종의 추상클래스이다. 추상클래스처럼 추상메서드를 갖지만 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며 그 외의 다른 어떠한 요소도 허용되지 않는다.


인터페이스 작성

	interface 인터페이스 이름 {
    	public static final 타입 상수이름 = 값;
        public abstract 메서드이름(매개변수목록);
    }

인터페이스 멤버들의 제약사항

  • 모든 멤버변수는 public static final 이어야 하며 이를 생략할 수 있다.
  • 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
    단 static메서드와 디폴트 메서드는 예외(JDK1.8부터)

	interface PlayingCard{
    	public static final int SPADE = 4;
        final int DIAMOND = 3;	// public static final int DIAMOND = 3;
        static int HEART = 2;	// public static final int HEART = 2;
        int CLOVER = 1;			// public static final int CLOVER = 1;
    }

원래는 인터페이스의 모든 메서드는 추상메서드이어야 하는데 JDK1.8부터 인터페이스에 static메서드와 디폴트 메서드의 추가를 허용하는 방향으로 변경되었다. JDK1.8이전의 규칙과 이후의 규칙을 알고 있는게 좋을 것 같다.


인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며 클래스와는 달리 다중상속 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다. 참고로 인터페이스는 클래스와 달리 Object클래스와 같은 최고 조상이 없다.

interface Movable{
	// 지정된 위치(x, y)로 이동하는 기능의 메서드
    void move(int x, int y);
}

interface Attackable{
	// 지정된 대상(u)을 공격하는 기능의 메서드
    void attack(Unit u);
}

interface Fightable extends Moveble, Attackable {  }

클래스의 상속과 마찬가지로 자손 인터페이스(Fightable)는 조상 인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받는다.
그래서 Fightable자체에는 정의된 멤버가 하나도 없지만 조상 인터페이스로부터 상속받은 두 개의 추상메서드 move(int x, int y)와 attack(Unit u)을 멤버로 갖게 된다.


인터페이스 구현

인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없다. 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 한다.
방법은 상속은 extends, 추상은 abstract 키워드 처럼 인터페이스는 implements로 사용하면 된다.

class 클래스이름 implements 인터페이스이름{
	// 인터페이스에 정의된 추상메서드를 구현해야 한다.
}
class Fighter implements Fightable {
	public void move(int x, int y) { //내용 생략 }
    public void attack(Unit u)     { //내용 생략 }
}

만일 구현하는 인터페이스의 메서드 중 일부만 구현하게 된다면 abstract를 붙여서 추상클래스로 선언해야 한다.

abstract class Fighter implements Fightable {
	public void move(int x, int y) { // 내용 생략 }
}

그리고 상속도 같이 구현할 수 있다.

class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { //내용 생략 }
    public void attack(Unit u)     { //내용 생략 }
}

예제1

public class FighterTest {
    public static void main(String[] args){
        Fighter f = new Fighter();

        if (f instanceof Unit)
            System.out.println("f는 Unit클래스의 자손입니다.");

        if (f instanceof Fightable)
            System.out.println("f는 Fightable인터페이스를 구현했습니다.");

        if (f instanceof Movalbe)
            System.out.println("f는 Movable인터페이스를 구현했습니다.");

        if (f instanceof Attackable)
            System.out.println("f는 Attackable인터페이스를 구현했습니다.");

        if (f instanceof Object)
            System.out.println("f는 Object클래스의 자손입니다.");

    }
}

class Fighter extends Unit implements Fightable{
    public void move(int x, int y){ }
    public void attack(Unit u) { }
}

class Unit{
    int currentHP;  // 유닛의 체력
    int x;          // 유닛의 위치 (x좌표)
    int y;          // 유닛의 위치 (y좌표)
}

interface Fightable extends  Movalbe, Attackable{  }
interface Movalbe    { void move(int x, int y); }
interface Attackable { void attack(Unit u); }

위 코드의 관계도이다.

Fighter클래스는 Unit클래스로부터 상속을 받고 Fightable인터페이스를 구현했다. Unit클래스는 Object클래스의 자손이고 Fightable은 Movalbe과 Attackable인터페이스의 구현체이다. 상속의 부모 자식 관계랑 비슷하다고 볼 수도 있다. 여기서 주의 깊게 봐야할 것은 Movalbe에 정의된 void move(int x, nit y)를 Fighter클래스에서는 구현할 때 접근제어자를 public으로 했다는 점이다.

오버라이딩 할 때 조상의 메서드보다 넓은 범위의 접근 제어자를 지정해야하 한다. Movable인터페이스에서 void move(int x, int y)로 되어있지만 사실 public abstract가 생략 되어있다. 그래서 구현할 때는 접근 제어자를 반드시 public으로 해야 한다.


인터페이스를 이용한 다형성

인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며 인터페이스 타입으로 형변환도 가능하다.

Fightable f = (Fightable)new Fighter();
또는
Fightable f = new Fighter();

따라서 인터페이스는 다음과 같이 메서드의 매개변수의 타입으로 사용될 수 있다.

void attack(Fightable f){
	//...
}

인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것이다.

그래서 attack메서드를 호출할 때는 매개변수로 Fightable인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다.

그리고 다음과 같이 메서드의 리턴타입이 인터페이스의 타입을 지정하는 것 역시 가능하다.

Fightable method() {
	Fighter f = new Fighter();
	return f;
}

리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.(반복해서 외울 것)


예제2

interface Parseable{
    // 구문 분석작업을 수행한다.
    public abstract void parse(String fileName);
}

class ParserManager{
    // 리턴타입이 Parseable인터페이스이다.
    public static Parseable getParser(String type){
        if(type.equals("XML")){
            return new XMLPaser();
        } else {
            Parseable p = new HTMLPaser();
            return p;
            // return new HTMLParser();
        }
    }
}

class XMLPaser implements Parseable{
    public void parse(String fileName){
        // 구문 분석작업을 수행하는 코드를 적는다.
        System.out.println(fileName + "- XML parsing completed.");
    }
}

class HTMLPaser implements Parseable{
    public void parse(String fileName){
        // 구문 분석작업을 수행하는 코드를 적는다.
        System.out.println(fileName + "- HTML parsing completed.");
    }
}

public class ParserTest {
    public static void main(String args[]){
        Parseable parser = ParserManager.getParser("XML");
        parser.parse("document.xml");
        parser = ParserManager.getParser("HTML");
        parser.parse("document2.html");
    }
}

Parseable parser = ParseManger.getParser("XML");

public static Parseable getParser(String type){
if(type.equals("XML")){
return new XMLPaser();
} else {
Parseable p = new HTMLPaser();
return p;
// return new HTMLParser();
}
}
Parseable의 구현체인 XMLParser와 HTMLParser이기 때문에 Parseable타입의 메서드로 정의하여 XML, HTML에 맞는 인스턴스를 생성해주고 해당 인스턴스의 메서드를 불러오는 것을 볼 수 있다.

이렇게 인터페이스의 다형성을 이용해서 결합도를 낮추고 코드가 간결해져 보기 쉽게 사용될 수 있다.


인터페이스 장점

  • 개발시간을 단축시킬 수 있다.
  • 표준화가 가능하다.
  • 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
  • 독립적인 프로그래밍이 가능하다.
  1. 개발시간을 단축시킬 수 있다.
    인터페이스가 작성되면 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다.
    그리고 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하게 하면 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.

  2. 표준화가 가능하다.
    프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.

  3. 서로 관계없는 클래스에게 관계를 맺어 줄 수 있다.
    서로 상속관계에 있지도 않고 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.

  4. 독립적인 프로그래밍이 가능하다.
    인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 대문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.


인터페이스 장점

인터페이스 본질적인 측면에서 알아보기 위해 두 가지 사항을 염두에 두고 있어야한다.

  • 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
  • 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 된다)

예제1

class A{
    public void methodA(B b){
        b.methodB();
    }
}

class B{
    public void methodB(){
        System.out.println("methodB ()");
    }
}

public class InterfaceTest {
    public static void main(String[] args){
        A a = new A();
        a.methodA(new B());
    }
}

위와 같은 경우 A클래스와 B클래스는 직접적인 관계를 갖고 있는 상태이다. 이럴 경우 w제공해주는 쪽(예제1 기준 class B)이 변경될 경우 사용하는 쪽(예제1 기준 class A)도 같이 변경되어야 하는 상황이 발생할 수 있다. 이렇게 되면 변경해야 할 코드도 많고, 시간도 더 걸리게 된다.

예제2

class A{
    void autoPlay(I i){
        i.play();
    }
}

interface I{
    public abstract void play();
}

class B implements I {
    public void play(){
        System.out.println("play in B class");
    }
}

class C implements I {
    public void play(){
        System.out.println("play in C class");
    }
}

public class InterfaceTest2 {
    public static void main(String[] args){
        A a = new A();
        a.autoPlay(new B());    // void autoplay(I i) 호출
        a.autoPlay(new C());    // void autoplay(I i) 호출
    }
}

예제2는 인터페이스를 이용하여 구현클래스들을 이용하여 인스턴스를 동적으로 제공 받은 예제이다.

이런 경우 제공해주는 쪽(예제2 기준 class B, class C)이 변경되더라도 사용하는 쪽(예제2 기준 class A)를 변경할 필요 없어진다. 이렇게 되면 코드를 많이 건드릴 필요도 없어지고 시간도 절약된다. 즉 유지보수 측면에서 용이해진다.


※ 참고 문헌

남궁성, 『Java의 정석 3nd Edition』, 도우출판(2016) 책으로 공부하고 정리한 내용 입니다.

profile
gyery
post-custom-banner

0개의 댓글