ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Java] 상속을 위한 관계 (IS-A, HAS-A) 그리고 오버라이딩
    Java/개념 정리 2020. 2. 20. 17:03

    상속을 위한 관계

    상속을 위한 관계? 그냥 extends 쓰고 상속받을 클래스를 쓰면 되는거 아닌가? 

     

     저는 처음 상속을 배웠을 때 위와 같이 단순하게 생각했습니다. 하지만 상속 관계를 구성하기 위해서는 조건이 필요하다는 사실! 물론 조건을 충족하지 않아도 상속관계를 구현 할 수는 있지만 조건을 충족 시키지 못하면 상속을 하는 의미가 없다는 뜻입니다. 이러한 상속을 위한 조건은 대표적으로 IS-A 관계가 있습니다. 또한 HAS-A 관계 역시 상속의 조건이 될 수 있습니다.

     

    IS-A 관계

     그렇다면 IS-A 관계란 무엇일까? 아주 간단합니다. 간단한 예를 들어 설명하겠습니다. 

     

    ( 전기자전거, 자전거), (무선마우스, 마우스) 

     

    혹시 눈치 채셨나요? 좀 더 직관적으로 설명하자면

     

    (전기자전거 IS A 자전거), (무선마우스 IS A 마우스)

     

    이제는 확실히 아시겠죠? 영어를 해석하면 "전기자전거는 일종의 자전거이다, 무선마우스는 일종의 마우스이다"가 됩니다. 마우스라하면 클릭 및 터치를 통한 입력이라는 기본기능이 있습니다. 무선마우스는 마우스의 기본기능에 무선이라는 '이동성'의 특징이 추가된 것입니다. 이런 경우 마우스를 상위 클래스로 무선마우스를 하위클래스로 정의함으로써 타당한 상속관계가 성립할 수 있습니다. 

     즉, 적절한 상속관계가 성립하기 위해서는 IS-A 관계를 성립해야 한다고 할 수 있습니다. 만약 IS-A 관계가 성립하지 않는다면 적절하지 않은 상속관계일 확률이 높습니다. 

     

    HAS-A 관계

     IS-A 관계 외에도 상속관계를 성립하기 위한 조건이 없는 것은 아닙니다. 바로 HAS-A 관계입니다. HAS-A 관계는 Have의 '가지다'라는 뜻처럼 소유의 관계를 말합니다. 상속이라는 것이 하위 클래스가 상위 클래스의 모든 것을 소유하기 때문에 소유의 관계로도 상속의 표현이 가능합니다. HAS-A 관계의 예시는 다음과 같습니다. 

     

    (사과 - 과일장수 )

     

    하지만 위의 예시를 HAS-A 관계로 표현을 할 경우 사과가 없을 때를 표현해야하고, 사과 외의 다른 과일이 추가될 경우 변경사항을 반영하기가 어렵습니다. Java는 다중상속을 지원하지 않는다는 사실! 망고를 추가한다면 망고 클래스를 과일장수에서 상속받자니 다중상속이 불가능해서 안돼고 사과 클래스에서 상속받자니 이건 뭐 애플망고가 되버리는건가? 이처럼 HAS-A 관계가 상속관계를 성립하는 조건이 될 수는 있지만 프로그램의 변경에 많은 제약을 가져다주는 조건인 것입니다. 

     

    메소드 재정의(feat. 오버라이딩)

    상위 클래스에서 정의된 메소드의 이름, 매개변수, 반환형까지 동일한 메소드를 하위클래스에서 재정의하면 어떻게 될까? 이것에 대한 답은 다음과 같습니다. 

     

    "참조변수를 이용해 인스턴스의 오버라이딩된 메소드를 호출하면 하위 클래스의 메소드가 호출된다"

    "하위 클래스에 오버라이딩된 상위 클래스의 메소드를 호출하고 싶다면 super 키워드를 사용한다"

     

    이 설명을 좀 더 쉽게 이해하기 위해 다음 코드를 확인합시다. 

    class Mouse {
    	private int sensitivity;
        	public setSensitivity(int sen) {
            	sensitivity = sen;
            }
        	public void showState(){
        		System.out.println("Sensitivity : "+sensitivity);
        	}
    }
    class WLMouse extends Mouse {
    	private boolean wl;
            public void showState() {
            	super.showState();
              	System.out.println("Wireless or not : "+ wl);
            }
        	public void setWl(boolean wireless) {
        		wl=wireless;
       	}
    }
    class Overriding {
    	public static void main (String[] args) {
        	WLMouse WLm = new WLMouse();
            WLm.setSensitivity(1000);
            WLm.setWl(true);
            WLm.showState();
        }
    }

    코드를 확인해보면 Mouse 클래스와 WLMouse 클래스에 void showState() 라는 동일한 메소드가 정의되어 있는 것을 확인 할 수 있습니다. showState() 메소드가 오버라이딩 된 것입니다.

     이제 main함수를 확인해보면 마지막 줄에 참조변수를 이용해서 인스턴스의 오버라이딩된 메소드를 호출하는 것을 확인 할 수 있습니다. 이때 WLMouse 클래스의 showState가 호출되게 됩니다.

     이제 호출된 WLMouse 클래스의 showState() 메소드를 확인해봅시다. 하위 클래스인 WLmouse 클래스에서 Mouse 클래스의 showState() 메소드를 호출할 때에는 super 키워드를 사용해 호출하는 것을 확인 할 수 있습니다. 

     

    상위클래스의 참조변수로도 하위 클래스의 인스턴스를 참조 가능할까?

    정답은 '가능하다'입니다. 즉, 위의 코드로 설명해보면 WLMouse WLm = new WLMouse(); 대신에 Mouse WLm = new WLMouse();가 가능하다는 것입니다. 위의 IS-A 관계의 설명을 보면 알 수 있듯이 무선마우스는 일종의 마우스입니다. 그렇게 때문에 상위클래스의 참조변수로도 하위 클래스의 인스턴스를 참조 가능한 것입니다. 즉, WLMouse의 인스턴스를 Mouse의 인스턴스로 바라본다는 것입니다. 그렇다면 아래 코드는 정상적으로 컴파일 될까?

    class Overriding {
    	public static void main (String[] args) {
        	Mouse WLm = new WLMouse();
            WLm.setSensitivity(1000);
            WLm.setWl(true);
            WLm.showState();
        }
    }

    정답은 WLm.setWl(ture); 메소드에서 컴파일에러가 발생합니다. 이유는 WLm이 실제로 참조하는 것은 WLMouse 인스턴스이지만, 참조하는 인스턴스가 Mouse의 참조변수이기 때문에 Mouse의 인스턴스로 인식하기 때문입니다. 즉, Mouse의 참조변수로 인스턴스를 참조하면 실제 참조하는 인스턴스와 상관없이 Mouse 클래스에 정의된 메소드만 호출이 가능합니다. 

     

    인스턴스 참조관계 일반화

    class Animal { . . . }
    class Camel extends Animal { . . . }
    class BactrianCamel extends Camel{ . . . }
    
    class Gen {
    	public static void main (String[] args) {
        		Animal me1 = new Camel();
            	Animal me2 = new BactrianCamel();
            	Camel me3 = new BactrianCamel();
    	}
    }

    위으 코드는 정상적으로 컴파일됩니다. 위 코드를 통해 알 수 있는 것은 Bactrian Camel 클래스는 Camel 클래스의 하위 클래스이면서 Animal 클래스의 하위클래스이기도 하다는 것입니다. 그렇다면 이런 경우는 어떨까?  

    Animal me1 = new BactrianCamel();
    Camel me2 = me1; // 컴파일 에러 발생
    BactrianCamel me3 = me1; // 컴파일 에러 발생

    me1이 참조하는 대상은 Animal과 Camel 클래스의 하위 클래스인 BactrianCamel 클래스의 인스턴스입니다. 그래서 많은 사람들은 위의 코드가 문제가 없다고 생각하는 경우가 있습니다. 하지만 앞서 위에서 설명했다시피 컴파일러는 me1이 참조하는 대상을 Animal 클래스의 인스턴스라고 생각합니다. 그렇기 때문에 위의 코드에서는 에러가 발생하게 됩니다. 위와 같은 형태로 me2와 me3를 선언해야한다면 형변환을 위한 코드를 추가해줘야 합니다.

    Animal me1 = new BactrianCamel();
    Camel me2 = (BactrianCamel)me1;
    BactrianCamel me3 = (BactrianCamel)me1;

    이렇게 말이죠. 그러나 이런 상황은 자주 발생하는 상황은 아닙니다. 이런 상황이 발생하게 된다면 형변환을 해주기보단 코드 구성을 다시 살펴보는게 좋습니다. 

    댓글

Camel`s Tistory.