[인프런 Java] 다형성 활용3

코드 속의 "진돌"·2024년 3월 22일
0
post-thumbnail

✅ 다형성 활용3


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

단축키

  • Inline Variable : command + option + N
  • Extract Method : command + option + M

✏️ AnimalPolyMain2

package poly.ex2;

public class AnimalPolyMain2 {

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

    Animal[] animalArr = {dog, cat, caw};

    // 변하지 않는 부분
    for (Animal animal : animalArr) {
      System.out.println("동물 소리 테스트 시작");
      animal.sound();
      System.out.println("동물 소리 테스트 종료");
    }
  }
}



🖥️ 실행 결과

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

Animal 타입의 배열을 만들고 다형적 참조를 사용하면 된다.

// 둘은 같은 코드이다.
Animal[] animalArr = new Animal[] {dog, cat, caw};
Animal[] animalArr = {dog, cat, caw};

다형적 참조 덕분에 Dog, Cat, Caw의 부모 타입인 Animal 타입으로 배열을 만들고, 각각을 배열에 포함했다.

이제 배열을 for문을 사용해서 반복하면 된다.

// 변하지 않는 부분
for (Animal animal : animalArr) {
	System.out.println("동물 소리 테스트 시작");
	animal.sound();
	System.out.println("동물 소리 테스트 종료");
}

animal.sound()를 호출하지만 배열에는 Dog, Cat, Caw의 인스턴스가 들어있다. 메서드 오버라이딩에 의해 각 인스턴스의 오버라이딩 된 sound() 메서드가 호출된다.




✅ 개선


이번에는 배열메서드 모두 활용해서 기존 코드를 완성해보자


✏️ AnimalPolyMain3

package poly.ex2;

public class AnimalPolyMain3 {

  public static void main(String[] args) {
    Animal[] animalArr = {new Dog(), new Cat(), new Caw()};
    
    for (Animal animal : animalArr) {
      soundAnimal(animal);
    }
  }

  // 동물이 추가되어도 변하지 않는 코드
  private static void soundAnimal(Animal animal) {
    System.out.println("동물 소리 테스트 시작");
    animal.sound();
    System.out.println("동물 소리 테스트 종료");
  }
}
  • Animal[] animalArr를 통해서 배열을 사용한다.
  • soundAnimal(Animal animal)
    • 하나의 동물을 받아서 로직을 처리한다.

새로운 동물이 추가되어도 soundAnimal(...) 메서드는 코드 변경 없이 유지할 수 있다. 이렇게 할 수 있는 이유는 이 메서드는 Dog, Cat, Caw 같은 구체적인 클래스를 참조하는 것이 아니라 Animal이라는 추상적인 부모를 참조하기 때문이다. 따라서 Animal을 상속 받은 새로운 동물이 추가되어도 이 메서드의 코드는 변경 없이 유지할 수 있다.

여기서 잘 보면 새로운 동물이 추가되었을 때 코드가 변하는 부분과 변하지 않는 부분이 있다.
main()은 코드가 변하는 부분이다. 새로운 동물을 생성하고 필요한 메서드를 호출한다.
soundAnimal(...)는 코드가 변하지 않는 부분이다.

새로운 기능이 추가되었을 때 변하는 부분을 최소화 하는 것이 잘 작성된 코드이다. 이렇게 하기 위해서는 코드에서 변하는 부분과 변하지 않는 부분을 명확하게 구분 하는 것이 좋다.




✅ 남은 문제


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

✓ Animal 클래스를 생성할 수 있는 문제

Animal 클래스는 동물이라는 클래스이다. 이 클래스를 다음과 같이 직접 생성해서 사용할 일이 있을까?

Animal animal = new Animal();

개, 고양이, 소가 실제 존재하는 것은 당연하지만, 동물이라는 추상적인 개념이 실제로 존재하는 것은 이상하다. 사실 이 클래스는 다형성을 위해서 필요한 것이지 직접 인스턴스를 생성해서 사용할 일은 없다.
하지만 Animal도 클래스이기 때문에 인스턴스를 생성하고 사용하는데 아무런 제약이 없다. 누군가 실수로 new Animal()을 사용해서 Animal의 인스턴스를 생성할 수 있다는 것이다. 이렇게 생성된 인스턴스는 작동은 하지만 제대로된 기능을 수행하지는 않는다.



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

예를 들어서 Animal을 상속 받은 Pig 클래스를 만든다고 가정해보자. 우리가 기대하는 것은 Pig 클래스가 sound() 메서드를 오버라이딩 해서 “꿀꿀”이라는 소리가 나도록 하는 것이다. 그런데 개발자가 실수로 sound() 메서드를 오버라이딩 하는 것을 빠트릴 수 있다. 이렇게 되면 부모의 기능을 상속 받는다. 따라서 코드상 아무런 문제가 발생하지 않는다. 물론 프로그램을 실행하면 기대와 다르게 “꿀꿀”이 아니라 부모 클래스에 있는 Animal.sound()가 호출될 것이다.


출처 : 김영한의 실전 자바 - 기본편
https://www.inflearn.com/course/김영한의-실전-자바-기본편/dashboard

profile
매일 성장하는 주니어 개발자의 기록📝

0개의 댓글