본문 바로가기
Web

[Java] 상위 클래스의 타입으로 변수 선언을 하는 이유

by DuncanKim 2022. 7. 3.
728x90

[Java] 상위 클래스의 타입으로 변수 선언을 하는 이유

- 다형성 추가.

 

작은 벽돌, 큰 벽돌, 회벽돌,,, 그들은 "벽돌"에 속한다.

 

1. 다형성에 대한 의문점 발동

 

이전에 다형성에 대해 알아본 적이 있다.

그 때는 이해했지만, 무엇인가 알기 어려운 '왜'라는 질문을 자꾸 품게 되었다.

 

2022.06.13 - [프로그래밍 언어/Java] - [Java] 다형성(Polymorphism) 알아보기

 

[Java] 다형성(Polymorphism) 알아보기

[Java] 다형성(polymorphism) 알아보기 칼은 참 다양한 종류가 있다. 요리할 때만 해도, 과일칼, 채소자르는 칼, 고기 다지는 칼, 빵칼 등등등.. 요리 용도가 아니라 도축, 수술 등등 많은 곳에서 쓰는

masterpiece-programming.tistory.com

 

(1) 굳이 상위 부모클래스의 타입으로 변수선언을 해서 그 안에 자식클래스의 인스턴스를 담는 것일까...?
그렇게 되면 자식클래스에서 새로 만들어놓은 메소드도 활용이 못하게 되는 것이 아닌가?

(2) 변수 선언을 할 때, 변수의 타입과 인스턴스의 타입을 동일하게 해주면 되는 것이 아닌가?

(3) 그러면 자식클래스로 선언을 해두고, 부모클래스로 인스턴스를 만들어서 담으면 되는 것이 아닌가...?
아니야 그러면 구현해놓은 무엇인가를 쓸 수가 없게 되잖아...

 

두 가지 질문이 자꾸 다음의 진도로 넘어가지 못하게 만들었다.

한 번 알아봤음에도 모르는게 나왔다면 제대로 짚고 넘어가야겠지...!

 

(1)과 (2)에 대해서 집중적으로 알아보고자 한다. (3)은 이미 알아보았으니까...!

 

 

2. 사례로 이해해보기

현대자동차의 '자동차'를 소개하는 클래스를 만들어보도록 하자.

현대자동차 안에는 '쏘나타'와 'G90'이라는 차가 있다. 이를 클래스로 구현하면 다음과 같다.

 

1) 자동차 클래스들

abstract class HyundaiCar{
    String name, color;
    public void setName(String name){
        this.name = name;
    }

    public void setColor(String color){
        this.color = color;
    }
    public String getName(){
        return name;
    }
    public String getColor(){
        return color;
    }
}

class Sonata extends HyundaiCar{

    @Override
    public void setName(String name) {
        this.name = "국민차 " + name ;

    }

    @Override
    public void setColor(String color) {
        this.color = "국민색깔 " + color ;
    }

    @Override
    public String getName() {
        return super.getName();
    }

    @Override
    public String getColor() {
        return super.getColor();
    }
}

class G90 extends HyundaiCar{
    @Override
    public void setName(String name) {
        this.name = "프리미엄 차 " + name ;

    }

    @Override
    public void setColor(String color) {
        this.color = "프리미엄 색깔 " + color ;
    }

    @Override
    public String getName() {
        return super.getName();
    }

    @Override
    public String getColor() {
        return super.getColor();
    }
}

현대자동차라는 클래스를 쏘나타, G90이라는 클래스가 상속받고 있다. 현대자동차 클래스는 추상클래스로, 하위클래스에게 본인이 가지고 있는 것 같은 메소드를 꼭 가져야 하는 것을 알려주고 있다.

 

쏘나타, G90 클래스 안에서는 각각 set, get을 활용하여 name과 color를 저장하고 있다. get은 현재 자신이 가지고 있는 변수를 내보내는 역할을 하고, set은 새로 입력된 변수를 입력받아 인스턴스 변수에 저장을 하는 역할을 한다.

구분을 짓기 위해 쏘나타는 입력한 값에 더해 "국민 ~~"라는 수식어가 붙게 되고, G90은 "프리미엄 ~~"라는 수식어를 가지게 된다.

 

2) 메인 클래스

public class Main {
    public static void main(String[] args) {
        HyundaiCar g90 = new G90();
        g90.setName("회장님차");
        g90.setColor("쥐똥");

        HyundaiCar gn7 = new Sonata();
        gn7.setName("개나소나타");
        gn7.setColor("투명한 블루");
    }
}

 

자 이렇게 되면, G90의 객체가 담긴 g90은 인스턴스 변수로 각각 "프리미엄 차 회장님차", "프리미엄 색 쥐똥"이 있을 것이고,

gn7의 경우, "국민자 개나소나타", "국민 색깔 투명한 블루"가 있을 것이다.

 

여기까지 했을 때, 왜 굳이 HyundaiCar라는 추상클래스를 타입으로 선언했는 지 잘 이해가 되지 않을 수 있다.

그렇다. "여기 까지" 했을 때는 전혀 필요없는 것이다.

무엇인가를 추가하고, 더 확장시킨다면, 이것에 대한 필요성을 바로 느낄 수 있다.

 

여기에 차종을 불문하고, 차의 이름과 색깔을 뽑을 수 있는 함수를 메인 클래스에 추가를 한다고 하자.

그러면 어떻게 해야 할까?

 

 

3) 완성된 메인 클래스

public class Main {
    public static void main(String[] args) {
        HyundaiCar g90 = new G90();
        g90.setName("회장님차");
        g90.setColor("쥐똥");

        HyundaiCar gn7 = new Sonata();
        gn7.setName("개나소나타");
        gn7.setColor("투명한 블루");
        
        introduce(gn7);
        introduce(g90);
    }
    
    public static void introduce(HyundaiCar car){
        System.out.println(car.getName()+"은(는) "+car.getColor()+"색입니다");
    }
}

 

자, 이렇게 하면 되지 않겠나...? HyundaiCar 타입을 가진 car이라는 변수를 집어넣었을 때, 메소드 안에서 메소드를 호출하는 것이 가능하다. 이렇게 되면, main 메소드 안에서 introduce 하나로 gn7과 g90의 차 이름과 색깔을 각각 출력할 수 있다.

 

 

잘 출력이 됨을 알 수 있다.

 

자 여기서 만약, 상위타입으로 선언을 하지 않고, 각각의 객체에 맞는 클래스로 타입선언을 했다면 어떻게 될까?

 

 

4) 상위 타입으로 선언하지 않은 변수 사용 시 코드

public class Main {
    public static void main(String[] args) {
        G90 g90 = new G90();
        g90.setName("회장님차");
        g90.setColor("쥐똥");

        Sonata gn7 = new Sonata();
        gn7.setName("개나소나타");
        gn7.setColor("투명한 블루");
        
        introduce(gn7);
        introduce2(g90);
    }
    
    public static void introduce(G90 car){
        System.out.println(car.getName()+"은(는) "+car.getColor()+"색입니다");
    }
    
    public static void introduce2(Sonata car){
    	System.out.println(car.getName()+"은(는) "+car.getColor()+"색입니다");
    }
}

 

이 경우, 메소드를 추가로 생성을 해야 한다. '중복'이 생겨버리는 것이다. 타입을 일치하여 선언을 하였으면, 하나의 함수로 해결이 가능했을 문제였는데, 각각 타입을 선언하여 구현이 두 배가 되었다는 것을 알 수 있다.

 

 

 

 

3. 정리하기 

인터페이스도 이와 비슷한 이유로 타입 선언을 할 때 소환이 되어지는 것이다.

자식클래스에서 구현해놓은 함수를 쓸 수 없는 불편함이 있지만, 그것이 매번 쓰이는 것이 아니라 한 번씩 쓰이는 것이라면, 형변환을 해서 한 번 씩 소환을 해주면 될 일이다.

 

클래스가 확장되고, 메소드가 많이 만들어질 수록 상위타입으로 변수 선언을 해야하는 필요성이 커지는 것이다.

 

정리를 해보면,

 

"프로젝트가 심화, 확장되었을 때의 코드 생산성을 위해,
그리고 유지보수의 편의성을 위해 상위타입으로 변수선언을 한다"

 

라고 정리를 할 수 있겠다.

728x90

댓글