5) 다형성

객체지향 프로그래밍의 특징 중에 다형성(폴리모피즘, Polymorphism)이라는 것이 있다.

 

도대체 폴리모피즘은 무엇이고 이게 왜 필요한 걸까?

 

역시 예제로 알아보도록 하자.

※ 객체지향 프로그래밍 챕터에서 사용되는 예제는 모두 연속되므로 앞에서부터 순서대로 예제를 따라해야 한다.

다음과 같이 Bouncer(경비원) 클래스를 만들어 보자. 경비원 클래스는 다음과 같이 동물을 짖게 하여 건물을 지킨다고 한다.

 

Bouncer.java


public class Bouncer {
    public void barkAnimal(Animal animal) {
        if (animal instanceof Tiger) {
            System.out.println("어흥");
        } else if (animal instanceof Lion) {
            System.out.println("으르렁");
        }
    }

    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        Lion lion = new Lion();

        Bouncer bouncer= new Bouncer();
        bouncer.barkAnimal(tiger);
        bouncer.barkAnimal(lion);
    }
}

 

barkAnimal 메소드는 입력으로 받은 animal 객체가 Tiger의 객체인 경우에는 "어흥"을 출력하고 Lion 객체인 경우에는 "으르렁"을 출력하게 한다.

※ instanceof 는 특정 객체가 특정 클래스의 객체인지를 조사할 때 사용되는 자바의 내장 키워드이다. animal instanceof Tiger 는 "animal 객체가 new Tiger로 만들어진 객체인가?" 를 묻는 조건식이다.

main 메소드를 위와 같이 작성하고 실행 해 보면 다음과 같은 결과값이 출력된다.

 


어흥
으르렁

 

여러분은 이 Bouncer 클래스의 barkAnimal 메소드가 마음에 드는가?

 

Crocodile, Leopard 등이 추가되면 barkAnimal 메소드는 다음처럼 수정되어야 할 것이다.

 


public void barkAnimal(Animal animal) {
    if (animal instanceof Tiger) {
        System.out.println("어흥");
    } else if (animal instanceof Lion) {
        System.out.println("으르렁");
    } else if (animal instanceof Crocodile) {
        System.out.println("쩝쩝");
    } else if (animal instanceof Leopard) {
        System.out.println("캬옹");
    }
}

 

흠, 별로 좋지 않다. 마음이 무거워 진다.

우리는 인터페이스를 배웠으므로 좀 더 나은 해법을 찾을 수 있을 것 같다.

자, 다음처럼 Barkable 이란 인터페이스를 작성 해 보자.

 

Barkable.java


public interface Barkable {
    public void bark();
}

그리고 Tiger클래스와 Lion 클래스가 Barkable 인터페이스를 구현하도록 변경 해 보자.

 

Tiger.java


public class Tiger extends Animal implements Predator, Barkable {
    public void bark() {
        System.out.println("어흥");
    }
}

 

인터페이스는 위에서 보듯이 콤마(,)를 이용하여 여러개를 implements 할 수 있다. Tiger 클래스는 Predator 인터페이스와 Barkable 인터페이스를 implements 하였다.

 

Tiger 클래스는 bark 메소드 실행 시 "어흥"을 출력하도록 했다.

 

Lion.java


public class Lion extends Animal implements Predator, Barkable {
    public void bark() {
        System.out.println("으르렁");
    }
}

 

Lion 클래스는 bark 메소드 실행 시 "으르렁"을 출력한다.

이렇게 Tiger, Lion 클래스에 bark 메소드를 구현하면 Bouncer 클래스의 barkAnimal 메소드를 다음처럼 수정할 수 있다.

 

바뀌기 전


public void barkAnimal(Animal animal) {
    if (animal instanceof Tiger) {
        System.out.println("어흥");
    } else if (animal instanceof Lion) {
        System.out.println("으르렁");
    }
}

 

바뀐 후


public void barkAnimal(Barkable animal) {
    animal.bark();
}

 

barkAnimal 메소드의 입력 자료형이 Animal에서 Barkable 로 변경되었다. 그리고 animal의 객체 타입을 체크하여 "어흥" 또는 "으르렁"을 출력하던 부분을 그냥 bark 메소드를 호출하도록 변경했다. 이렇게 변경했더니 복잡하던 조건분기문도 사라지고 누가봐도 명확한 코드가 되어 버렸다. Amazing!!

※ 폴리모피즘을 이용하면 위의 예에서 보듯이 복잡한 if else 의 조건문을 간단하게 처리할 수 있는 경우가 많다.

 

위 예제에서 사용한 tiger, lion 객체는 각각 Tiger, Lion 클래스의 객체이면서 Animal 클래스의 객체이기도 하고 Barkable, Predator 인터페이스의 객체이기도 하다. 이러한 이유로 barkAnimal 메소드의 입력 자료형을 Animal에서 Barkable 로 바꾸어 사용할 수 있는 것이다.

 

이렇게 하나의 객체가 여러개의 자료형 타입을 가질 수 있는 것을 객체지향 세계에서는 다형성, 폴리모피즘(Polymorphism)이라고 부른다.

 

즉 Tiger 클래스의 객체는 다음과 같이 여러가지 자료형으로 표현할 수 있다.

 


Tiger tiger = new Tiger();
Animal animal = new Tiger();
Predator predator = new Tiger();
Barkable barkable = new Tiger();

 

여기서 알아두어야 할 사항은 Predator 로 선언된 predator 객체와 Barkable 로 선언된 barkable 객체는 사용할 수 있는 메소드가 서로 다르다는 점이다. predator 객체는 getName() 메소드가 선언된 Predator 인터페이스의 객체이므로 getName 메소드만 호출이 가능하다. 이와 마찬가지로 Barkable 로 선언된 barkable 객체는 bark 메소드만 호출이 가능하다.

 

만약 getName 메소드와 bark 메소드를 모두 사용하고 싶다면 어떻게 해야 할까?

 

Predator, Barkable 인터페이스를 구현한 Tiger 로 선언된 tiger 객체를 사용하거나 다음과 같이 getName, bark 메소드를 모두 포함하는 새로운 인터페이스를 새로 만들어 사용하면 된다.

 

BarkablePredator.java


public interface BarkablePredator  {
    public void bark();
    public String getName();
}

 

또는

 


public interface BarkablePredator extends Predator, Barkable {
}

 

두번 째 방법은 기존의 인터페이스를 활용하는 방법이다. 두 번째 방법대로 하면 Predator의 getName 메소드, Barkable의 bark 메소드를 그대로 상속받을 수 있다.

 

인터페이스는 일반 클래스와는 달리 extends 를 이용하여 여러개의 인터페이스(Predator, Barkable)를 동시에 상속할 수 있다. 즉, 다중 상속이 지원된다. (※ 일반 클래스는 단일상속만 가능하다.)

 

자, 다음은 최종적으로 완성된 Barkable 인터페이스와 Tiger, Lion, Bouncer 클래스이다.

 

Barkable.java


public interface Barkable {
    public void bark();
}

 

Tiger.java


public class Tiger extends Animal implements Predator, Barkable {
    public void bark() {
        System.out.println("어흥");
    }
}

 

Lion.java


public class Lion extends Animal implements Predator, Barkable {
    public void bark() {
        System.out.println("으르렁");
    }
}

 

Bouncer.java


public class Bouncer {
    public void barkAnimal(Barkable animal) {
        animal.bark();
    }

    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        Lion lion = new Lion();

        Bouncer bouncer = new Bouncer();
        bouncer.barkAnimal(tiger);
        bouncer.barkAnimal(lion);
    }
}