ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 12. 자바 메서드 오버라이딩과 다형성
    자바(Java) 강의 2019. 4. 12. 13:49

    지난번 포스트에서 수퍼 클래스의 메서드를 그대로 이용하면서도 내가 원하는 기능(행동)을 하도록 바꿀 수 없을까?라는 질문을 했었다. 이번 포스트에서는 그 질문의 답 중 하나인 '메서드 오버라이딩'에 대해 알아보도록 한다. 또 메서드 오버라이딩이 객체 지향 프로그래밍에서 가지는 의미에 대해서도 설명하도록 한다.

    이전 포스트

    목표

    • 프로젝트 구조
    • Animal, Cat, Dog 클래스
    • 메서드 오버라이딩 (Overriding)
    • 다형성 (Polymorphism)

    프로젝트 구조

    이 포스트의 내용을 실습하기 위해 아래와 같은 자바 프로젝트 구조를 따랐다.

    JavaTutorial
    ├── JavaTutorial.iml
    └── src
        ├── Main.java
        └── animal
            ├── Animal.java
            ├── Cat.java
            └── Dog.java
    
    2 directories, 5 files

    패키지 - animal

    Animal, Cat, Dog 클래스

    Animal, Cat, Dog 클래스는 다음과 같은 관계를 가졌다.

    클래스 다이어그램(Class Diagram)

    Animal은 Cat과 Dog의 수퍼클래스이다.

    Animal.java

    package animal;
    
    public class Animal {
        public String name;
        
        public Animal(String name) {
            this.name = name;
        }
    
        public void eat(int portion) {
            System.out.println(name + "(이)는 " + portion + " 만큼의 밥을 먹었다. ");
        }
    }

    Cat과 Dog 클래스는 다음과 같다.

    Cat.java

    package animal;
    
    public class Cat extends Animal{
        public Cat(String name) {
            super(name);
        }
    }

     Dog.java

    package animal;
    
    public class Dog extends Animal{
    
        public Dog(String name) {
            super(name);
        }
    }

    그리고 메인 메서드에서 아래처럼 선택된 동물의 오브젝트를 생성하고 eat메서드를 부른다.

    import animal.Animal;
    import animal.Cat;
    import animal.Dog;
    
    import java.util.Scanner;
    
    public class Main {
        public static void main(String[] args) {
            Animal animal;
            int animalChoice = getUserChoice();
            if (animalChoice == 1) {
                animal = new Cat("토미");
            } else {
                animal = new Dog("하이디");
            }
            animal.eat(10);
        }
    
        // 터미널 또는 cmd로부터 정수를 입력받는 메서드
        private static int getUserChoice() {
            Scanner scan = new Scanner(System.in);
            System.out.print("원하는 동물을 입력하세요. 1. 고양이 토미, 2. 강아지 하이디 : ");
            return scan.nextInt();
        }
    }

    위와 같은 코드를 이용해 사용자의 입력에 따라 고양이 또는 강아지 오브젝트를 실행시간(Runtime)에 생성할 수 있다. 이 코드를 실행하고 1을 입력하며 다음과 같은 결과가 나온다.

    원하는 동물을 입력하세요. 1. 고양이 토미, 2. 강아지 하이디 : 1
    토미(이)는 10 만큼의 밥을 먹었다. 

    우리의 목표는 무언인가? Cat 오브젝트가 생성되는 경우 '토미(이)는 10 만큼의 밥을 먹었다. 야옹야옹!'을 출력하고, Dog 오브젝트가 생성되는 경우 '하이디(이)는 10만큼의 밥을 먹었다. 멍뭉멍뭉!'을 출력하는 것이었다. 이전 포스트에서는 이 문제를 해결하기 위해 meow라는 메서드를 만들거나 eat이라는 메서드의 매개변수를 바꿨었다. 이 포스트에서는 메인 메서드는 여러분이 고칠 수 없는 ReadOnly 파일이라고 가정해보자. 이 상황을 어떻게 해결할 것인가?

    메서드 오버라이딩 (Overridding)

    다행히 자바에서는 메서드 오버라이딩(Overriding)이라는 기능을 제공한다. 메서드 오버라이딩이란 서브 클래스가 수퍼 클래스의 메서드를 덮어쓰기 할 수 있는 기능이다. 예를 통해 무슨 말인지 확인해보자.

    package animal;
    
    public class Cat extends Animal{
        public Cat(String name) {
            super(name);
        }
    
        @Override
        public void eat(int portion) {
            System.out.println(name + "(이)는 " + portion + " 만큼의 밥을 먹었다. 야옹야옹!");
        }
    }

    Animal 클래스에 eat이라는 메서드가 정의되어있다. 이 수퍼클래스(Animal) 메서드의 리턴 타입, 메서드 이름, 매개변수가 똑같은 메서드를 서브클래스(Cat)에 정의하면 메서드 오버라이딩이 된다. 앞으로 cat.eat()을 부르면 Animal에 있는 eat 메서드가 아니라, Cat에 있는 eat 메서드를 부른다는 뜻이다. 이제 메인 메서드를 실행하고 1을 눌러 고양이 토미 오브젝트를 만들어보자.

    원하는 동물을 입력하세요. 1. 고양이 토미, 2. 강아지 하이디 : 1
    토미(이)는 10 만큼의 밥을 먹었다. 야옹야옹!

    강아지 하이디 오브젝트를 만들면 어떻게 되는가?

    원하는 동물을 입력하세요. 1. 고양이 토미, 2. 강아지 하이디 : 2
    하이디(이)는 10 만큼의 밥을 먹었다. 

    Dog 클래스는 메서드 오버라이딩을 하지 않았기 때문에 Animal메서드에 있는 eat을 사용한다.

    연습하기

     Dog 오브젝트로 '하이디(이)는 10 만큼의 밥을 먹었다. 멍뭉멍뭉!'을 출력하게 하기 위해 Dog클래스에서 eat메서드를 오버라이딩 해보자.

     

    메서드 오버라이딩의 기능은 무엇인가? 만약 수퍼클래스의 메서드가 서브클래스에서 오버라이딩 되었다면, 서브클래스의 메서드를 사용한다. 그렇지 않으면 수퍼클래스의 메서드를 사용한다. 

    이를 이용해 우리는 메인 메서드를 수정하거나 새 메서드를 만들지 않고도 기능을 변경할 수 있다. 메인 메서드를 수정하지 않아도 된다는 게 왜 중요한가? 지금은 메인 메서드에서만 eat을 이용하지만, 큰 프로그램에서는 메인 메서드 말고도 100개의 다른 클래스에서 eat을 사용하고 있을 수 있다. 메서드 오버라이딩을 이용하면 Dog나 Cat클래스 하나만 수정해도 100개의 다른 클래스의 행동(Behavior)이 달라진다. 100개의 다른 클래스를 100번 수정해주지 않아도 말이다. 이런 점에서 메서드 오버라이딩을 이용하면 코드의 확장성(Extenextensibility)을 높이고 코드 유지보수(Maintenance)를 용이하게 한다.

    다형성 (Polymorphism)

    import animal.Animal;
    import animal.Cat;
    import animal.Dog;
    
    import java.util.Scanner;
    
    public class Main {
        public static void main(String[] args) {
            Animal animal; // animal은 Dog오브젝트일 수도 Cat오브젝트일 수도 있다. 아래에서 유저가 뭘 입력하냐에 따라 달라짐.
            int animalChoice = getUserChoice();
            if (animalChoice == 1) {
                animal = new Cat("토미");
            } else {
                animal = new Dog("하이디");
            }
            animal.eat(10); 
        }
    
        // 터미널 또는 cmd로부터 정수를 입력받는 메서드
        private static int getUserChoice() {
            Scanner scan = new Scanner(System.in);
            System.out.print("원하는 동물을 입력하세요. 1. 고양이 토미, 2. 강아지 하이디 : ");
            return scan.nextInt();
        }
    }

    위의 예에서 어떤 작업을 했는지 다시 살펴보자. 수퍼클래스 Animal이 있고, Cat과 Dog은 이 클래스를 상속(Inheritance)했다. 그리고 실행 시에 유저의 입력에 따라 Animal형의 변수 animal에 Cat오브젝트 또는 Dog오브젝트를 생성해 할당할 수 있었다. 즉 같은 Animal형이지만 실행 시 동적(Dynamic)으로 다른 오브젝트(Dog 또는 Cat)를 할당할 수 있다.

    또, Animal에는 eat이라는 메서드가 있었다. 근데 우리는 이 eat메서드의 기능이 마음에 들지 않았다. eat메서드를 그대로 사용하면서도, 그 안의 기능(코드 부분)만 바꾸기 위해 Cat과 Dog클래스에서 eat 메서드를 오버라이드 했다. 이제 Cat과 Dog 오브젝트는 같은 Animal 타입의 변수에 할당되지만 이 Animal타입의 변수 안에 든 오브젝트의 eat 메서드는 해당 오브젝트가 Cat 오브젝트인지 Dog 오브젝트인지에 따라 달라진다. 이처럼 하나의  메서드(eat)가 여러 개의 다른 형태/기능(Dog의 eat 또는 Cat의 eat)을 가지는 것을 객체 지향 프로그래밍에서는 '다형성(Polymorphism)'이라고 부른다. 자바는 다형성(Polymorphism)을 지원하기 위해 여러 가지 기능을 제공하는데, 메서드 오버라이딩이 그중 하나이다. 

    이번 포스트에서는 메서드 오버라이딩과 다형성에 대해 알아보았다. 만약 수퍼클래스에서 eat메서드를 미리 구현하고 싶지 않다면 어떻게 할까? 예를 들어서 talk이라는 메서드를 만든다고 치자. 모든 동물을 각각 우는 방법이 다르다. 고양이는 야옹, 강아지는 멍멍, 새는 짹짹하고 운다. 이런 경우 수퍼클래스에서 talk이라는 메서드를 어떻게 만들어야 하는가? 다음 포스트에서는 공통된 기능이지만 그 구현이 다른 경우 각 서브클래스에서 무조건 메서드를 오버라이드 하도록 하는 기능, 즉 추상 클래스와 추상 메서드에 대해서 알아보도록 하겠다.

     

    연습하기

    새 동물친구를 만들어보자!

    우리는 동물친구 키우기 게임을 만들고 싶다. 이를 위해 사용자가 직접 새 동물 친구의 이름을 정할 수 있게 하고 싶다. 아래의 코드를 수정하여 동물 키우기 다마고치 유저가 새 동물친구를 만들 수 있게 해 보자.

    import animal.Animal;
    import animal.Cat;
    import animal.Dog;
    
    import java.util.Scanner;
    
    public class Main {
        public static void main(String[] args) {
            Animal animal;
            String name = getAnimalName();
            int animalChoice = getUserChoice();
            if (animalChoice == 1) {
                animal = new Cat("토미");
            } else {
                animal = new Dog("하이디");
            }
            animal.eat(10);
        }
    
        // 터미널 또는 cmd로부터 정수를 입력받는 메서드
        private static int getUserChoice() {
            Scanner scan = new Scanner(System.in);
            System.out.print("원하는 동물을 입력하세요. 1. 고양이 토미, 2. 강아지 하이디 : ");
            return scan.nextInt();
        }
    
        private static String getAnimalName() {
            // 이 부분의 코드를 짜 보자. getUserChoice와 비슷하게 짜면 된다.
        }
    }

    실행 시 아래처럼 출력되도록 위의 코드를 수정해 보도록 하자. 입력 값으로 '냥몬', '1'을 준 결과이다.

    새 동물 친구의 이름: 냥몬
    냥몬은 어떤 동물인가요?
    1. 고양이
    2. 강아지
    1
    냥몬(이)는 10 만큼의 밥을 먹었다. 야옹야옹!

    portion도 입력받을 수 있는가? 할 수 있다면 portion도 입력받을 수 있도록 해보자.

    새 동물 친구의 이름: 냥몬
    냥몬은 어떤 동물인가요?
    1. 고양이
    2. 강아지
    1
    밥을 얼마나 줄까요? 30
    냥몬(이)는 30 만큼의 밥을 먹었다. 야옹야옹!

    다음 포스트: 13. 자바 추상 클래스와 추상 메서드

    댓글

f.software engineer @ All Right Reserved