프롬프트 AI&OpenAPI&공공데이터를 활용한 웹앱개발자 양성 과정 7일차

서명원·2023년 12월 17일

1. 상속

아래 코드에서 Rabit은 부모클래스인 Animal을 extends 키워드를 통해 상속하고 있다.

부모 클래스를 상속 받으면 아래 그림처럼 자식은 부모가 가지고있는 메소드를 따로 정의하지 않아도 사용할 수 있게된다.

또한 아래처럼 부모에게 물려받은 메소드를 재정의 하여 사용할 수도 있는데, 이걸 오버라이딩이라고 한다.

아래 그림을 보면 pc.b에 빨간 줄이 떠있다.
참조용 변수에 인스턴스를 연결하는 것을 리모컨으로 비유하기도 하는데, 이 경우는 리모컨에 없는 버튼을 누르려는 경우라고 할 수 있다.
tv에 예약 종료 기능이 구현되어 있다고 해도, 리모컨에 예약종료 버튼이 없다면 리모컨으로 해당 기능을 작동 시킬 수 없을 것이다.

마찬가지로 이번엔 리모컨에 tv미러링이나 넷플릭스같은 많은 버튼이 있다고 생각해보자.
근데 tv는 구형 tv라 미러링은 커녕 넷플릭스도 지원이 안된다.
기능을 더많이 가진 tv가, 기능이 더 적은 리모컨의 버튼에 맞춰 행동할 순 있지만(자식 타입이 부모 타입으로 형전환 할 수 있지만), 기능을 더 적게 가진 tv가 기능을 더 많이가진 리모컨의 버튼에 맞춰 행동 할 수는 (부모타입이 자식 타입으로 형변환 할 수는) 없는거다.

참조형의 형변환이란 개념을 들었을 때 머리속에 정리가 되지 않았다.
참조형의 변수는 메모리 주소값만 가지고 있고, 실제 객체는 힙영역에 생성 될텐데, 변수의 타입에 맞춰 객체가 형변환 한다는게 대체 무슨의미일까?
참조형 변수가 형전환 되면, 참조유형(Reference Type)만 변경되게 된다. 실제 힙 메모리에 생성된 객체 자체가 변하는게 아니라, 객체에 접근하는 방법이 변경되는 것이다.

또한 상속은 여러 부모로부터 받을 수가 없다.
여러 부모에 같은 동일한 메서드가 정의 되어있으면 충돌 문제가 발생하기 때문이다.

2. 과제- 생각하며 코딩하기

다음과 같은 과제가 나왔다.
다음 코드에서 고무2오리가 고무오리를 상속 받지않게하며, 중복코드를 제거하시오.

 
class Main {
  public static void main(String[] args) {
    청둥오리 a청둥오리 = new 청둥오리();
    a청둥오리.날다();
    // 출력 : 오리가 날개로 날아갑니다.
    
    흰오리 a흰오리 = new 흰오리();
    a흰오리.날다();
    // 출력 : 오리가 날개로 날아갑니다.
    
    고무오리 a고무오리 = new 고무오리();
    a고무오리.날다();
    // 출력 : 저는 날 수 없어요. ㅜㅠ
    
    고무2오리 a고무2오리 = new 고무2오리();
    a고무2오리.날다();
    // 출력 : 저는 날 수 없어요. ㅜㅠ
  }
}

class 오리 {
  void 날다() {
    System.out.println("오리가 날개로 날아갑니다.");
  }
}


class 흰오리 extends 오리 {
  
}

class 청둥오리 extends 오리 {
  
}

class 고무오리 extends 오리 {
  void 날다() {
    System.out.println("저는 날 수 없어요. ㅜㅠ");
  }
}

class 고무2오리 extends 오리 {
  void 날다() {
    System.out.println("저는 날 수 없어요. ㅜㅠ");
  }
}

처음 생각한 해결책: 날다 라는 메소드에 true나 false 값을 매개변수로 주고, 해당 매개변수로 출력을 제어하기.

 
class 오리 {
  void 날다(boolean is_ply) {
    if(boolean is_ply){
      System.out.println("오리가 날개로 날아갑니다.");
    }else{
      System.out.println("저는 날 수 없어요. ㅜㅠ");
    }
  }
}

문제점: 메소드를 호출하는 main 메소드가 직접 결과값을 제어하는 것과 마찬가지인 코드가 되어버린다.
자식 클래스에 따라서 다른 동작을 하도록 구현되어야 하는데, 이래서는 단지 매개변수에 따라 다른 동작을 하게 되어버린다.

해결안2 : 오리 내부에 boolean타입의 멤버필드를 추가해 그걸로 컨트롤 하면 어떨까?
아래와 같이 오리 클래스를 정의하고 오리를 상속받은 각 오리 클래스 생성자에서 is_ply값을 재정의하면 되지 않을까?


class 오리 {
boolean is_ply = true;
void 날다() {
if(is_ply) {
System.out.println("오리가 날개로 날아갑니다.");
}else {
System.out.println("저는 못 날아요. ㅜㅠ");
}

단점: 만약 오리의 종류와 나는 방식의 구현이 늘어나면 오리 클래스가 비대해지고, 그 경우 오리클래스를 상속받은 모든 클래스에 미치는 영향을 고려해야 된다.

해결안3 : 비행 방식을 정의하는 interface를 만들고, 오리와 오리를 상속받은 클래스에서 적절한 비행의 구현 클래스를 호출하도록 하면 어떨까?


class 오리 {
	비행 ply = new 날개짓();
	
	void 날다() {
		ply.날다();
	}
}
class 흰오리 extends 오리 { }
class 청둥오리 extends 오리 { }
class 고무오리 extends 오리 {
	고무오리(){
		this.ply = new 못날다();
	}
}
class 고무2오리 extends 오리 {
	고무2오리(){
		this.ply = new 못날다();
	}
}



interface 비행{
	abstract void 날다();
}
interface 수영{
	abstract void 수영하다();
}

class 날개짓 implements 비행 {

	@Override
	public void 날다() {
		System.out.println("오리가 날개로 날아갑니다.");
		
	}
}

class 못날다 implements 비행 {

	@Override
	public void 날다() {
		System.out.println("저는 못 날아요. ㅜㅠ");
		
	}
}

	

이렇게 코딩해있을 때 장점은 아래와 같다고 생각한다.

  1. 비행 방법의 세부 사항이 변경되도 같은 비행 방법을 공유하는 클래스 들에 일관적인 변경이 가능하다.
  2. 비행방법의 세부가 변경되도, 메소드를 호출하는 main이나, 비행방법을 사용하는 오리 클래스들을 수정할 필요가 없다.

3. 복잡한 상속

다단계 상속 (Multilevel Inheritance): 한 클래스가 다른 클래스를 상속하고, 그 클래스가 또 다른 클래스를 상속하는 구조를 다단계 상속이라고 한다.
이 경우 클래스 간의 관계가 복잡해 질 수 있고, 클래스의 변경이 여러 계층에 영향을 미칠 수 있다.

4. 추론적 사고

아래 코드에서 도출 해 낼 수 있는 정보.

int i = 로봇.get정수();

로봇 class에 메소드명이 get정수인 static 타입의 메소드가 정의되있는데, return type은 int형이고, 매개변수는 없다.

IDE의 자동 완성에만 의존하지 말고, 코드에 담긴 의미와 역할을 추론할 수 있어야 한다.

5. interface

abstract 추상 매서드
선언만 되고 구현은 제공되지 않는 메서드다.
메서드의 시그니처만 정의하고, 실제 구현은 하위 클래스에 맡긴다.

interface의 경우 추상메서드, 상수, 디폴트메서드, 정적메서드만 선언이 가능하다.
다중 상속은 메소드 충돌 리스크가 있어서 불가능 하지만, interface의 메서드는 선언만 되고 구현은 구현체의 몫이기 때문에 다중 구현이 가능하다.
자바 8 이후로 추후 interface를 구현한 여러 객체에 동일한 변경사항이 필요할 경우를 위해, 디폴트 메서드나 정적 메서드가 추가되었지만, 시그니처 충돌등의 리스크가 있으므로 조심해서 사용해야한다.

profile
백엔드 취업을 꿈꾸는 일본어 전공자

0개의 댓글