10장: 상속과 코드 재사용

상속과 중복 코드

  • DRY 원칙

    • Don’t Repeat Yourself라는 뜻으로, 동일한 것을 중복하지 말라는 것이다.

    • 모든 지식은 시스템 내에서 단일하고, 애매하지 않고, 정말로 믿을 만한 표현 양식을 가져야 한다.

  • 중복이라고 판단하는 기준은 요구사항이 변경되었을 때 두 코드를 함께 수정해야 하는가이다.

  • 중복 코드는 새로운 중복 코드를 부른다. 그리고 이를 추가하는 과정에서 코드의 일관성이 무너지고 버그가 발생할 수 있으며 코드의 변경 속도가 느려진다.

  • 이미 존재하는 클래스와 유사한 클래스가 필요하다면 코드를 복사하지 말고 상속을 이용해 코드를 재사용할 수 있다.

  • 상속을 이용해 코드를 재사용하기 위해서는 부모 클래스의 개발자가 세웠던 가정이나 추론 과정을 정확하게 이해해야 한다.

  • 이로 인해 상속은 부모 클래스와 자식 클래스 간의 결합도를 높이며 코드를 수정하기 어렵게 만든다.

  • 자식 클래스의 메서드 안에서 super 참조를 이용해 부모 클래스의 메서드를 직접 호출할 경우 두 클래스는 강하게 결합된다. super 호출을 제거할 수 있는 방법을 찾아 결합도를 제거해야 한다.

    • 예를 들면 Phone 클래스의 요금을 계산하는 calculateFee 메서드와 이를 상속받은 NightlyDiscountPhone 클래스의 calculateFee 메서드가 있다고 한다. 이 때 NightlyDiscountPhone 클래스의 calculateFee 메서드는 super.calculateFee 를 호출한다. 이 때 세금을 부과하는 요구사항이 추가되면, Phone 클래스의 calculateFee 메서드에 세금을 부과하는 로직을 적용하고, NightlyDiscountPhone 클래스의 calculateFee 메서드에서 야간 할인을 적용하는 경우에도 세금을 부과하는 로직을 적용해야 한다.

    public class Phone {
    	// ...
    	public Money calculateFee() {
    		return result.plus(result.times(taxRate));
    	}
    	public double getTaxRate() {
    		return taxRate;
    	}
    }
    
    public class NightlyDiscountPhone extends Phone {
    	// ...
    	@Override
    	public Money calculateFee() {
    		// ...
    		return result.minus(nightlyFee.plus(nightlyFee.times(getTaxRate()))); 
    	}
    }

취약한 기반 클래스 문제

  • 부모 클래스의 변경에 의해 자식 클래스가 영향을 받는 현상

  • 부모 클래스의 작은 변경에도 자식 클래스는 컴파일 오류와 실행 에러가 발생할 수 있다.

  • 상속은 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만들기 때문에 캡슐화를 약화시킨다.

  • 상속받은 부모 클래스의 메서드가 자식 클래스의 내부 구조에 대한 규칙을 깨트릴 수 있다.

    • Java Util의 Vector 클래스를 상속받는 Stack 클래스는 맨 마지막 위치에서만 요소를 추가/제거할 수 있는 메서드 뿐만 아니라 get, add 등과 같이 원하는 원소를 조회/추가할 수 있는 Vector 클래스의 메서드를 사용할 수 있으므로 Stack의 규칙을 파괴하는 안좋은 예시이다.

  • 자식 클래스가 부모 클래스의 메서드를 오버라이딩할 경우 부모 클래스가 자신의 메서드를 사용하는 방법에 자식 클래스가 결합될 수 있다.

  • 클래스가 상속되기를 원한다면 상속을 위해 클래스를 설계하고 문서화해야 하며, 그렇지 않은 경우에는 상속을 금지시켜야 한다. 하지만 내부구현을 드러내야 하는 것은 결국 상속이 캡슐화를 위반하기 때문에 발생하는 것이다.

  • 클래스를 상속하면 결합도로 인해 자식 클래스와 부모 클래스의 구현을 영원히 변경하지 않거나, 자식 클래스와 부모 클래스를 동시에 변경하거나 둘 중 하나를 선택할 수밖에 없다.

추상화

  • 어떤 클래스가 있다면 무작정 상속하는 대신 추상화한 객체를 상속받도록 해야 한다.

  • 어떤 메서드의 중복을 제거하기 위해 재정의하고 있다면, 이에서 공통된 부분을 뽑아 추상 클래스의 메서드로 두고 변하는 부분은 추상 메서드로 재정의하도록 한다.

  • 아래는 Phone이라는 추상 클래스를 기본 요금 정책을 사용하는 RegularPhone과 야간 할인 정책을 사용하는 NightlyDiscountPhone이 상속받는 구조를 보여준다.

  • 자식 클래스는 자신의 인스턴스를 생성할 때 부모 클래스에 정의된 인스턴스 변수를 초기화해야 하기 때문에 자연스럽게 부모 클래스에 추가된 인스턴스 변수는 자식 클래스의 초기화 로직에 영향을 미치게 된다.

차이에 의한 프로그래밍

  • 기존 코드와 다른 부분만을 추가함으로써 애플리케이션의 기능을 확장하는 방법

  • 상속을 이용하면 이미 존재하는 클래스의 코드를 쉽게 재사용할 수 있기 때문에 애플리케이션의 점진적인 정의(incremental definition)가 가능해진다.

  • 중복 코드를 제거해 하나의 모듈로 모아 코드를 재사용한다면 코드의 품질을 유지하면서 코드 작성에 들이는 노력과 테스트를 줄일 수 있다.

Last updated