다형적 참조
public class Parent {
      public void parentMethod() {
          System.out.println("Parent.parentMethod");
    } 
}
public class Child extends Parent {
      public void childMethod() {
          System.out.println("Child.childMethod");
    } 
}
 public class PolyMain {
     public static void main(String[] args) {
         Parent poly = new Child(); // 다형적 참조
         poly.parentMethod();
    }
}
  • 부모 타입의 변수가 자식 인스턴스를 참조(Parent poly = new Child())
  • 이 경우 자식 타입인 Child를 생성했기 때문에 메모리 상에 Child와 Parent가 모두 생성
  • 생성된 참조값을 Parent 타입의 변수인 poly에 담아둠
  • 반대로 자식 타입은 부모 타입을 담을 수 없음

다형적 참조와 인스턴스 실행

다형적 참조(ref.인프런)

  • poly.parentMethod()를 호출하면 먼저 참조값을 사용해서 인스턴스를 찾음
  • 다음으로 인스턴스 안에서 실행할 타입도 찾음
  • 위의 경우 Parent 클래스부터 시작해서 필요한 기능을 찾음
  • 인스턴스의 Parent 클래스에 parentMethod()를 호출

다형적 참조의 한계

다형적 참조의 한계(ref.인프런)

  • 호출자인 poly는 Parent 타입이므로 Parent 클래스부터 시작해서 필요한 기능을 찾음
  • 그런데 상속 관계는 부모 방향으로 찾아 올라갈 수는 있지만 자식 방향으로 찾아 내려갈 수는 없음
  • Parent는 부모 타입이고 상위에 부모가 없기때문에 childMethod()를 찾을 수 없으므로 컴파일 오류 발생

다형성과 캐스팅
Child child = (Child) poly // Parent poly
  • (타입) 처럼 괄호와 그 사이에 타입을 지정하면 참조 대상을 특정 타입으로 변경 가능
  • 특정 타입으로 변경하는 것을 캐스팅이라고 함

1. 다운캐스팅

public class TestMain {
	public static void main(String[] args) {
        //부모 변수가 자식 인스턴스 참조(다형적 참조)
        Parent poly = new Child();
        //단 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
        //poly.childMethod();
		
        //다운캐스팅 - 다운캐스팅 결과를 변수에 담아두는 과정이 필요
        Child child = (Child) poly;
        child.childMethod();
        
        //일시적 다운캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
         ((Child) poly).childMethod();
      }
}

다운 캐스팅(ref.인프런)
일시적 다운 캐스팅(ref.인프런)

  • 자식 타입으로 변경하는 것을 다운캐스팅
  • 캐스팅을 한다고 해서 Parent poly의 타입이 변하는 것은 아님
  • 해당 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 되는 것
  • 따라서 poly의 타입은 Parent로 그대로 유지
  • 일시적 다운캐스팅을 사용하면 별도의 변수 없이 인스턴스의 자식 타입의 기능을 사용할 수 있음

2. 업캐스팅

public class TestMain {
      public static void main(String[] args) {
      Child child = new Child();
      Parent parent1 = (Parent) child; //업캐스팅은 생략 가능, 생략 권장
      Parent parent2 = child; //업캐스팅 생략
      
      parent1.parentMethod();
      parent2.parentMethod();
      }
}
  • 부모 타입으로 변경하는 것을 업캐스팅
  • 업캐스팅은 생략 가능
  • 자바에서 부모는 자식을 담을 수 있음

다운캐스팅 주의점
public class TestMain {
	public static void main(String[] args) {
    	Parent parent1 = new Child();
        Child child1 = (Child) parent1;
        child1.childMethod();
        
        Parent parent2 = new Parent();
        Child child2 = (Child) parent2; // 런타임 오류 - ClassCastException
        child2.childMethod(); // 실행 불가
	}
}
  • new Parent()로 부모 타입으로 객체를 생성. 따라서 메모리 상에 자식 타입은 존재하지 않음
  • parent2를 Child 타입으로 다운캐스팅 하지만 메모리 상에 Child는 존재하지 않아 사용 불가
  • 사용할 수 없는 타입으로 다운캐스팅하는 경우에 ClassCastException 예외 발생

다운캐스팅이 가능한 경우 메모리 구조

다운캐스팅이 불가능한 경우 메모리 구조

업캐스팅과의 차이

 

업캐스팅(ref.인프런)

  • 업캐스팅의 경우 객체 생성 시 해당 타입의 상위 부모 타입은 모두 함께 생성
  • 따라서 위로만 타입을 변경하는 업캐스팅은 메모리 상에 인스턴스가 모두 존재하기 때문에 안전
    • new C()로 인스턴스를 생성하면 인스턴스 내부에 자신과 부모인 A, B, C 모두 생성
    • A a = new C() : A로 업캐스팅
    • B b = new C() : B로 업캐스팅
    • C c = new C() : 자신과 같은 타입

다운캐스팅(ref.인프런)

  • 객체를 생성하면 부모 타입은 모두 함께 생성되지만 자식 타입은 생성되지 않기 때문에 다운캐스팅의 경우 인스턴스에 존재하지 않는 하위 타입으로 캐스팅하는 경우 문제 발생
    • new B()로 인스턴스를 생성하면 인스턴스 내부에 자신과 부모인 A, B 모두 생성
    • A a = new B() : A로 업캐스팅
    • B b = new B() : 자신과 같은 타입
    • C c = new B() : 하위 타입 대입 불가, 컴파일 오류
    • C c = (C) new B() : 강제 다운캐스팅, 하지만 B 인스턴스에 C와 관련된 부분이 없으므로 ClassCastException 런타임 오류 발생

'Java > Basic' 카테고리의 다른 글

다형성과 메서드 오버라이딩  (0) 2024.12.31
instanceof  (0) 2024.12.31
상속  (0) 2024.12.12
static  (1) 2024.12.08
메모리 구조  (2) 2024.12.08
다형성의 본질
  • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
  • 클라이언트(호출하는 코드)를 변경하지 않고, 서버(호출 당하는 코드)의 구현 기능을 유연하게 변경할 수 있다.

중요점

  • 유연하며 변경이 용이
  • 확장 가능한 설계
  • 클라이언트에 영향을 주지 않는 변경 가능
  • 인터페이스를 안정적으로 잘 설계하는 것이 중요

한계

  • 역할(인터페이스) 자체가 변하면 클라이언트, 서버 모두 큰 변경이 발생
  • 인터페이스를 안정적으로 잘 설계하는 것이 중요

SOLID
  • SRP(single responsibility principle) : 단일 책임 원칙
  • OCP(Open/closed principle) : 개방-폐쇄 원칙
  • LSP(Liskov substitution principle) : 리스코프 치환 원칙
  • ISP(Interface segregation principle) : 인터페이스 분리 원칙
  • DIP(Dependency inversion principle) : 의존관계 역전 원칙

SRP 단일 책임 원칙
  • 한 클래스는 하나의 책임만 가져야 한다.
  • 하나의 책임이라는 것은 모호
    • 클 수 있고, 작을 수 있음
    • 문맥과 상황에 따라 다름
  • 중요한 기준은 변경
    • 변경이 있을 때 파급효과가 적으면 단일 책임 원칙을 잘 따른 것
  • ex) UI 변경, 객체의 생성과 사용을 분리

OCP 개방-폐쇄 원칙
  • 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
  • 다형성을 활용
  • 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현
public class OrderServiceImpl implements OrderService {
	//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

문제점

  • OrderServiceImpl 클라이언트가 구현 클래스(FixDiscountPolicy, RateDiscountPolicy)를 직접 선택
  • 구현 객체를 변경하려면 클라이언트 코드(호출 하는 코드)를 변경해야 함
  • OCP 원칙 위반
  • 해결 방법 : 객체를 생성하고, 연관관계를 맺어주는 별도의 조립을 위한 설정자가 필요(스프링 컨테이너가 진행)

LSP 리스코프 치환 원칙
  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면, 이 원칙이 필요
  • ex) 자동차 인터페이스의 엑셀은 앞으로 가라는 기능으로, 뒤로 가게 구현하면 LSP 위반

ISP 인터페이스 분리 원칙
  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
  • 정비 인터페이스 자체가 변해도 운전 클라이언트에 영향을 주지 않음
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다.

DIP 의존관계 역전 원칙
  • 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻
  • 역할(Role)에 의존하게 해야 한다는 것과 같다.
  • 구현체에 의존하게 되면 변경이 아주 어려줘진다.
public class OrderServiceImpl implements OrderService {
	//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

문제점

  • 인터페이스에 의존하지만, 구현 클래스도 동시에 의존
  • OrderServiceImpl 클라이언트가 구현 클래스를 직접 선택(RateDiscountPolicy)
  • DIP 위반

'Java > Spring Basic' 카테고리의 다른 글

@ComponentScan  (0) 2024.08.21
@Configuration  (0) 2024.08.20
Singleton  (0) 2024.08.20
스프링 컨테이너  (0) 2024.08.17
AppConfig  (0) 2024.08.15

+ Recent posts