프로그래밍언어/Java

[Java 기본] 다형성 활용

배세 2024. 7. 25. 15:15

다형성 활용


지금까지 다형성에 대해 공부했다. 그런데 이 다형성은 대체 어떻게 활용하는 것일까?

이해를 돕기 위해 먼저 다형성을 사용하지 않는 코드를 작성해 보고, 이후 다형성을 사용하도록 코드를 변경해 보겠다. 

 

예제1


 

다형성을 활용하지 않는 예제이다. 

개, 고양이, 소의 울음 소리를 테스트하는 프로그램을 작성해보자. 

 

Dog.java

public class Dog {

    public void sound() {
        System.out.println("멍멍");
    }
}

 

Cat.java

public class Cat {

    public void sound() {
        System.out.println("야옹");
    }
}

 

Caw.java

public class Caw {

    public void sound() {
        System.out.println("음메");
    }
}

 

AnimalSoundMain.java

public class AnimalSoundMain {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();

        System.out.println("동물 소리 테스트 시작");
        dog.sound();
        System.out.println("동물 소리 테스트 종료");

        System.out.println("동물 소리 테스트 시작");
        cat.sound();
        System.out.println("동물 소리 테스트 종료");

        System.out.println("동물 소리 테스트 시작");
        caw.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

 

실행결과

 

=> 만약 여기서 새로운 동물이 추가되면 어떻게 될까? Main 에 계속 코드가 추가될 것이다. 코드가 중복되는 느낌이 들지 않는가?

그럼 중복을 어떻게 제거할 수 있을까?

 

반복되는 부분을 메서드로 분리해보자. 

private static void sound(Caw caw) {
    System.out.println("동물 소리 테스트 시작");
    caw.sound();
    System.out.println("동물 소리 테스트 종료");
}

=> 좋은 방법이 아니다. 매개변수의 타입이 Caw로 정해져 있기 때문이다. Cat이나 Dog는 부를 수 없다. 

 

반복문을 사용하는 것은 어떨까? 동물의 타입이 다르기 때문에 하나의 배열에 담을 수 없다.

Dog, Cat, Caw의 타입이 서로 다르기 때문에 중복을 제거하기가 어렵다. 동물이 같은 타입을 사용할 수 있다면 메서드와 배열을 활용해서 코드의 중복을 제거할 수 있다. 

 

다형성의 핵심은 다형적 참조와 메서드 오버라이딩이다. 이 둘을 활용하면 Dog, Cat, Caw가 모두 같은 타입을 사용하고, 각자 자신의 메서드도 호출할 수 있다. 

 

다형성을 활용해서 예제를 수정해보자. 

 

예제2


다형성을 사용하기 위해 상속 관계를 사용한다. Animal이라는 부모 클래스를 만들고 sound() 메서드를 정의한다. 이 메서드는 자식 클래스에서 오버라이딩 할 목적으로 만들었다. 

Dog, Cat, Caw 는 Animal 클래스를 상속 받는다. 그리고 각 부모의 sound() 메서드를 오버라이딩 한다. 

 

Animal.java

public class Animal {
    
    public void sound() {
        System.out.println("동물 울음 소리");
    }
}

 

Dog.java

public class Dog extends Animal{

    @Override
    public void sound(){
        System.out.println("멍멍");
    }
}

 

Cat.java

public class Cat extends Animal{
    
    @Override
    public void sound() {
        System.out.println("냐옹");
    }
}

 

Caw.java

public class Caw extends Animal {

    @Override
    public void sound() {
        System.out.println("음메");
    }
}

 

AnimalPolyMain.java

public class AnimalPolyMain1 {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();

        soundAnimal(dog);
        soundAnimal(cat);
        soundAnimal(caw);
    }

    private static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

 

 

실행결과

=> 동일한 실행결과가 출력되었다. 

다형적 참조 덕분에 animal 변수는 자식인 Dog, Cat, Caw의 인스턴스를 참조할 수 있다. 또한 메서드 오버라이딩을 사용함으로써 animal.sound() 를 호출해도 각각의 동물 클래스가 가지고 있는 sound() 메서드가 호출된다. 

새로운 동물을 추가해도 메인메서드를 그대로 사용할 수 있다. 

 

이번에는 배열과 for문을 사용해서 중복을 제거해보자. 

 

AnimalPolyMain2.java

public class AnimalPolyMain2 {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();
        Animal[] animals = {dog, cat, caw};
        
        for (Animal animal : animals) {
            System.out.println("동물 소리 테스트 시작");
            animal.sound();
            System.out.println("동물 소리 테스트 종료");
        }
    }
}

 

=> 배열은 같은 타입의 데이터를 나열할 수 있다. Dog, Cat, Caw는 모두 Animal의 자식이므로 배열을 생성할 수 있다. 

 

코드를 좀 더 개선해보자. 

public class AnimalPolyMain2 {

    public static void main(String[] args) {
        Animal[] animals = {new Dog(), new Cat(), new Caw()};

        for (Animal animal : animals) {
            soundAnimal(animal);
        }
    }
    
    public static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

 

=> 처음 코드와 비교해보자. 

새로운 기능이 추가되어도 변경을 최소화하는 코드가 잘 작성된 코드이다. 처음 코드와 비교해 보자. 변경할 부분이 줄어든 것을 확인할 수 있다. 

 

남은 문제


아직 코드에 남아있는 문제점이 있다. 

  • Animal 클래스를 생성할 수 있는 문제
Animal animal = new Animal();
animal.sound();

 => 이렇게 코드를 작성하면 컴파일 에러가 발생한다. 

  • Animal 클래스를 상속 받는 곳에서 sound() 메서드를 오버라이딩 하지 않을 가능성

좋은 프로그램은 제약이 있는 프로그램이다. 추상 클래스와 추상 메서드를 사용하면 이런 문제를 한번에 해결할 수 있다. 

 

 

 


* 인프런 
'김영한의 실전 자바 - 기본편'을 참고하여 작성하였습니다.