상속

SeokHwan An·2023년 4월 5일
0

java

목록 보기
7/10

우아한테크코스 블랙잭 미션 중 중복된 코드를 제거라하라 는 요구사항이 있었습니다. 그 이유는 블랙잭의 경우 딜러와 플레이어의 객체가 필요했는데 이 둘은 공통적으로 하는 행위를 가지고 있었기 때문입니다. 이를 하나로 묶기 위해 상속을 고려했습니다. 이번 미션을 진행하면서 제가 느낀 상속에 대해서 정리해 보는 시간을 가지려고 합니다.

상속이란

자바에서 상속은 extends 키워드를 이용해서 구현할 수 있는 객체 지향 프로그래밍의 기본 개념 중에 하나로 기존 클래스의 메소드와 변수를 이용할 수 있는 새로운 클래스를 생성하는 것을 의미합니다. 이 때 기존의 클래스를 부모 클래스(상위 클래스)라고 부르며 새롭게 생긴 클래스를 자식 클래스(하위 클래스)라고 부릅니다.

상속의 큰 특징은 부모 클래스(상위 클래스)의 변화에 따라 자식 클래스(하위 클래스)가 큰 영항을 받는다는 것입니다. 부모 클래스의 메소드가 변하게 된다면 자식의 클래스 역시 결과가 변화하게 된다. 즉, 상속은 is-a의 관계일 때 적용하는 것이 좋습니다.

상속을 왜 사용하는지

이번 블래잭 미션을 적용해서 상속을 이용했을 때 얻을 수 있는 장점에 대해 알아보겠습니다.

1. 중복되는 코드를 제거할 수 있다.

블랙잭의 경우 Dealer와 Player는 공통된 행위를 가지고 있습니다.

  • 카드를 뽑는 기능
  • ACE 카드를 가지고 있는지 체크 하는 기능 (ACE 카드는 때로는 1점 혹은 11점으로 계산될 수 있기 때문에)
  • 자신이 현재 블랙잭인지 판단하는 기능 (블랙잭 : 처음 받은 두 장의 점수의 합이 21인 경우)

과 같이 공통되는 기능을 가지게 됩니다. 이를 Dealer 클래스와 Player 클래스에 모두 구현하는 것은 코드 중복을 유발하게 됩니다.

public class Dealer {

    public void hit(Card card) {
        // 카드를 받는 기능
    }

    public boolean hasAceCard() {
        // ace 카드를 가지고 있는지 검증하는 기능
    }
    
    public boolean isBlackJack() {
        // 블랙잭을 확인하는 기능
    }
}

public class Player {

    public void hit(Card card) {
        // 카드를 받는 기능
    }

    public boolean hasAceCard() {
        // ace 카드를 가지고 있는지 검증하는 기능
    }

    public boolean isBlackJack() {
        // 블랙잭을 확인하는 기능
    }
}

상속을 이용하면 공통된 로직을 중복하지 않고 Dealer와 Player가 공통된 기능들을 가질 수 있습니다.

public class Participant {

    public void hit(Card card) {
        // 카드를 받는 기능
    }

    public boolean hasAceCard() {
        // ace 카드를 가지고 있는지 검증하는 기능
    }
    
    public boolean isBlackJack() {
        // 블랙잭을 확인하는 기능
    }
}

public class Dealer extends Participant {
  
}

public class Player extends Participant {
  
}

2. 다형성을 적용할 수 있다.

상속을 이용하면 자동 타입 변환이 적용되어 다형성을 구현할 수 있습니다.

💡자동 타입 변환이란
컴파일러가 자동으로 타입을 변환하는 것을 의미합니다.
상속에서는 기본적으로 자식 클래스(하위 클래스)는 부모 클래스(상위 클래스)과 동일하게 취급이 됩니다.
ex) Participant dealer = new Dealer();

즉, 상위 클래스에서 Dealer와 Player 각각을 위한 메소드를 만들 필요 없이 하나의 메소드로 다형성을 적용할 수 있습니다.

public class BlackjackGame {
		...

		public int calculateDealerScore(Dealer dealer) {
				// 딜러 점수 계산
		}
			
		public int calculatePlayerScore(Player player) {
				// 플레이어 점수 계산
		}

		// 상속은 다형성 적용이 가능하기 때문에 위의 두 방법은 하나의 코드로 변경할 수 있습니다.
		
		public int calculateScore(Participant participant) {
				// 카드 점수 계산하기
		}
} 

위의 코드는 미션에서 직접 이용한 코드는 아니지만 상속의 다형성을 이용한 코드라고 볼 수 있습니다. Dealer와 Player 각각의 점수를 계산하는 메소드를 만들 필요 없이 상위 클래스(Participant)를 인자로 받아서 하나의 메소드로 처리할 수 있습니다. 이 부분 역시 중복되는 코드를 제거한다고 볼 수 있습니다.

지금까지 미션을 진행하면서 상속을 이용했을 때 느꼈던 장점에 대해 설명해보았습니다. 장점만 보면 중복되는 코드가 발생할 때 마다 상속을 고려할 만큼 좋은 장점을 가지고 있지만 치명적인 단점을 가지고 있습니다.

상속을 이용할 때 고려해야할 점

1. 상위 클래스의 변경에 하위 클래스도 그래도 적용이 되는가?

제가 상속을 이용하면서 느낀 점은 요구사항 변화에 쉽게 대응하는 것이 어려울 수 있겠다. 였습니다. 이와 같이 느낀 이유는 상위 클래스에 변화가 생겼을 때 이를 하위 클래스에서 그대로 적용이 된다면 상관이 없겠지만 하위 클래스에서는 기존에 방식을 유지해야 한다면 이는 오히려 유지보수를 하는데 더 많은 리소스가 필요하다고 생각됩니다. 즉, is-a관계가 지속적으로 유지가 되지 않을 때 상속을 이용한다면 요구사항의 변화가 발생했을 때 더 큰 문제를 가져올 수 있다는 것을 느낄 수 있었습니다.

2. 설계 시 상속이 깊어지는데 그대로 상속을 유지해야할까?

상속이 깊어지면 하위 클래스 생성이나 메소드 호출 시 오버헤드가 발생하지 않을까? 이 부분은 상속을 처음 학습하는 순간부터 고려했습니다. 상속은 하위 클래스를 호출하면 상위 클래스 역시 JVM의 메모리 영역중 하나인 heap 메모리에 같이 올라가게 됩니다. 만약에 상속이 계속 깊어지게 되고 그 때마다 메소드를 오버라이딩 해야한다면 클래스와 메소드 모두가 heap 메모리 영역에 올라가게 되는데 이는 오버헤드가 발생하게 될 테고 성능상에도 좋지 못할 것이라고 생각했습니다. 즉, 상속이 깊어지게 된다면 계속 상속을 유지하는 것이 좋은지에 대해 생각해볼 시점이라는 것을 느낄 수 있었습니다.

결론

상속은 중복된 코드 제거와 객체지향 프로그래밍의 핵심인 다형성을 이용할 수 있다는 큰 장점을 가지고 있습니다. 하지만 상위 클래스와 하위 클래스가 강한 결합을 가지고 있어서 상위 클래스 변화에 하위 클래스도 영향을 받게 됩니다. 또한 상속이 깊어지는 상황에서는 중복 코드를 줄이는 것 이상으로 큰 단점을 가지고 올 수 있는데 이는 성능상의 문제점을 야기할 수 있습니다. 그렇기에 상속을 이용할 때에는 앞서 설명했던 문제점이 발생하지 않는지 고려해보고 적용하는 것이 좋다고 생각합니다.

0개의 댓글