점프투자바

SUADI·2022년 5월 8일

6. 객체지향 프로그래밍

(7) 인터페이스(Interface)

ㄱ) 상속

인터페이스란 극단적으로 동일한 목적 하에 동일한 기능을 수행할 수 있록 강제하는 장치이다. 지금까지는 클래스를 몇 개 다루지 않았지만 클래스가 수없이 많다고 가정하고, 그 클래스마다 같은 기능을 수행해야 한다고 가정해 보자. 그렇다면 지금까지 배웠던걸 활용해서 구현해 본다면 상속을 사용해서 한 클래스에 정의한 메소드를 다른 수많은 클래스에 적용할 수 있을 것이다. 예를 들어보자면 사육사(Zookeeper)가 수많은 동물(Animal)들에게 각기 다른 먹이(feed)를 주는 코드를 짜보면

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }

    String getFood() {
        return "food";
    }
}

class Dog extends Animal {
    String getFood() {
        return "apple";
    }
}

class Cat extends Animal {
    String getFood() {
       return "banana";
    }
}

class Lion extends Animal {

}

class Zookeeper extends Animal{
    void feed(Animal animal) {
        System.out.println("feed " + animal.getFood());
    }
}

public class Sample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Lion lion = new Lion();
        Zookeeper zookeeper = new Zookeeper();

        zookeeper.feed(dog);
        zookeeper.feed(cat);
        zookeeper.feed(lion);
    }
}

(코드 설명에 앞서 이 코드는 내가 작성한 거라 적절하게 작성했는지 잘모르겠다..)

  • Animal 클래스에 getFood 메소드를 생성한 후에 자식 클래스인 Dog, Cat 클래스에 getFood 메소드를 오버라이딩해서 각 동물이 먹을 먹이를 리턴하게끔 했다. (Lion 클래스는 Interface를 사용했을 때와 비교하기 위해 오버라이딩하지 않은 상태로 두었다.)

  • 그 다음 Zookeeper 클래스에 feed 메소드를 생성하여 main메소드에서 각 동물별로 먹이를 준다는 문구를 출력하도록 했다.(feed메소드의 입력인자로 Animal을 왜 쓰는지 이런 세세한 부분가지 코드를 이해하지 못하고 있다..Animal을 입력인자로 쓰면 자식 클래스의 메소드를 사용할 수 있어서인가..?)

  • 메인메소드에 Dog, Cat, Lion, Zookeeper의 객체를 생성하고 객체 zookeeper의 feed메소드를 불러와 각 동물의 객체를 입력값에 넣으면 다음과 같이 출력된다.

feed apple
feed banana
feed food
  • 여기서 주의깊게 봐야하는 것은 lion 객체를 입력값으로 넣었을 때의 출력 결과이다. Lion 클래스에서는 getFood 메소드를 오버라이딩하지 않았기 때문에 Animal 클래스의 getFood 메소드의 리턴값인 food가 출력되었다.

ㄴ) 인터페이스

지금까지 배웠던 상속을 이용해서 코드를 작성하면 자식 클래스가 부모 클래스의 메소드를 오버라이딩하지 않고도 사용이 가능하기 때문에 해당 메소드를 반드시 구현해야 한다는 "강제성"을 갖지 않는다. 반면 인터페이스는 위에 설명했듯이 동일한 목적 하에 동일한 기능을 수행할 수 있도록 "강제"하는 장치이다.

interface Predator {
	String getFood();
}

class Animal {
	String name;
    
    void setName(String name) {
    	this.name = name;
    }
}

class Dog extends Animal implements Predator {
	public String getFood() {
    	return "apple";
    }
}

class Cat extends Animal implements Predator {
	public String getFood() {
    	return "banana";
    }
}

class Lion extends Animal implements Predator {
	public String getFood() {
    	return "strawberry";
    }
}

class Zookeeper {
	public void feed(Predator predator) {
    	System.out.println("feed " + predator.getFood());
    }
}

public class Sample {
	public static void main(String[] args) {
    	Dog dog = new Dog();
        Cat cat = new Cat();
        Lion lion = new Lion();
        Zookeeper zookeeper = new Zookeeper();
        
        zookeeper.feed(dog);
        zookeeper.feed(cat);
        zookeeper.feed(lion);

    }
}
  • interface Predator를 생성한다. class와 마찬가지로 보통은 파일을 새로 만들어 따로 코드를 작성해야 하지만 편의상 같은 파일에 작성하였다. interface의 작성 규칙은 메소드의 바디를 작성하지 않고 implement하는 클래스에서 내용을 작성하게 되어 있다.

  • 각 동물의 클래스에 Animal 클래스를 상속하고 interface Predator를 implements(수행, 구현)한다. interface에서 getFood 메소드의 바디를 작성하지 않았으므로 각 클래스에서 동물들이 먹을 먹이를 리턴하도록 작성한다. 작성할 때의 주의점은 interface의 메소드는 항상 public으로 구현해야 한다.(왜인지는 모르겠다.)

  • Zookeeper 클래스에서는 feed 메소드를 생성한 후 각 동물 별로 어떤 먹이를 먹이는지 문구를 출력하도록 한다. feed 메소드의 입력인자는 Predator로 한다. dog, cat, lion은 각각 Dog, Cat, Lion의 객체이기도 하지만 interface Predator의 객체이기도 하다. 이와 같이 두개 이상의 자료형 타입을 갖게 되는 특성을 다형성(Polymorphism)이라고 한다. 다형성은 다음 시간에 공부할 것이다.

  • 메인메소드에서 각 클래스의 객체를 생성한 후 predator 객체의 메소드 feed 메소드를 통해 각 동물의 객체를 입력인자로 넣으면 어떤 먹이를 먹는지 문구를 출력하게 한다.

  • 여기서 첫번째 코드인 상속을 이용한 코드처럼 Lion 클래스에 getFood 메소드를 적지 않는다면 컴파일 오류가 난다. 인터페이스를 사용하면 각 클래스가 동일한 기능을 무조건 사용해야 하기 때문이다. 상황에 따라 강제성을 띄는 인터페이스를 사용할지 자유로운 상속을 사용해야할지 결정하면 된다.

. . .
class Lion extends Animal implements Predator {

}
. . .
java: Lion is not abstract and does not override abstract method getFood() in Predator

ㄷ) 디폴트 메소드(Default Method)

  • 위에 언급했듯이 인터페이스는 기능에 대한 선언만 가능하기 때문에 실제 코드를 구현한 로직은 포함될 수 없다. 하지만 자바8부터는 인터페이스에서 메소드를 생성할 때 default를 명시하면 기능에 대한 선언 뿐만 아니라 바디를 채울 수 있는 기능이 추가되었는데 이를 디폴트 메소드라고 한다.

  • 디폴트 메소드 기능을 추가하게 된 배경은 예를 들어서 내가 오픈소스코드를 만들었는데 그 코드가 유명해져서 많은 사람들이 사용한다고 가정하자. 오픈소스코드를 사용하다가 인터페이스 내의 메소드를 수정하거나 추가해야하는 상황이 생긴다면 내가 만든 오픈소스코드를 사용하는 많은 사람들은 오류가 발생할 수 있고, 코드를 수정해야 하는 상황이 발생하여 호환성이 떨어질 수 있다. 이러한 경우 디폴트 메소드를 사용하면 호환성을 유지하면서 인터페이스를 보완할 수 있다.(인터넷 서칭 결과로 어느정도 이해는 했지만 그 상황이 닥쳐봐야 제대로 이해를 할 수 있을 것 같다.)

interface Predator {
	String getFood();
    
    default void printFood() {
    	System.out.printf("My food is %s\n",getFood());
    }
}
...
	public static void main(String[] args) {
    	...
        dog.printFood();
        cat.printFood();
        lion.printFood();
    }
My food is apple
My food is banana
My food is strawberry

0개의 댓글