오브젝트 Object 요약 10~11

Mr_Gu·2022년 1월 17일
0

books

목록 보기
7/8
post-thumbnail

10. 상속과 코드 재사용

중복 코드

DRY(Don't Repeat Yourself) : 중복을 만들지 말라. 한 번, 단 한번 원칙 또는 단일 지점 제어 원칙으로 불린다.

중복 코드의 폐해는 잘 알꺼라고 생각한다. 더 심각한 건 중복을 없애지 않고 중복 코드를 그대로 둔다면 더 큰 중복을 불러 일으킨다는 것이다.

참고로 여기서 말하는 중복은 단순히 코드의 모양이 같은게 아니다. 코드의 모양이 비슷하지만 변경 지점이 다르다면 중복이 아니다. (로그인과 회원가입 기능 코드가 똑같다고 하나로 뭉치면 안된다.)

상속

상속을 코드 재사용을 위해서 사용할 수 있다.
하지만 상속은 상속해주는 부모 클래스와 상속받는 자식 클래스를 강하게 묶어버린다. 별 생각 안하고 상속하다간 부모 클래스와 자식 클래스이 하나의 덩어리처럼 뭉쳐버릴 것이다.

구체적으로 어떤 문제가 생기는지 알아보자

불필요한 인터페이스 상속 문제 <= Stack Class가 Vector Class의 index add 기능 관련 인터페이스를 그대로 상속함.
메서드 오버라이딩의 문제. <= 상속은 부모의 메서드 구현 방식을 자세히 알아야하는 단점이 있다.
부모 클래스와 자식 클래스의 동시 수정 문제

  • 취약한 기반 클래스 문제(Fragile Base Class Problem, Brittle Base Class Problem)
    상속을 사용한다면 피할 수 없는 근본적인 취약성이다.

일단 개선

  • 추상화에 의존하기
    두 메서드가 유사하게 보인다면 차이점을 메서드로 추출하고 공통점을 부모 클래스로 보내버려라.
    변하는 것(차이점들)으로부터 변하지 않는 것(공통점)을 분리하자.
public abstract class AbstractPhone {
  //...
  public Money calculateFee() {
    //...
    result = result.plus(calculateCallFee(call));
  }
  abstract protected Money calculateCallFee(Call call);
}

public class Phone extends AbstractPhone {
    //...
    @Override
    protected Money calculateCallFee(Call call){
    	//...
    }
}

public class NightlyDiscountPhone extends AbstractPhone {
    //...
    @Override
    protected Money calculateCallFee(Call call){
    	//...
    }
}

구체 클래스는 추상 클래스에 의존
추상 클래스 내에서 구체 메서드는 추상 메서드에 의존.
최대한 구체적인 아이들은 추상적인 아이들에 의존하도록 하였다.

  • 의도를 드러내는 이름

데이터 왔다갔다하는 함수 이름 붙일때 get, set만 붙이지 말고 협력의 맥락이 드러나는 이름을 붙여주자. (ep buy, sell)

그래도..

만약 세금을 부과하는 로직을 추가한다면 어떤 변화가 일어날까요?

공통 로직 부분인 calculateFee만 고치면 될 듯 하지만 아니다. 새로운 맴버 변수가 추가되기 때문에 자식 클래스 모두 super 키워드로 새로운 멤버 변수를 등록받아야 한다.

상속은 공통점을 추출해서 추상화에 의존하고 이름을 깔끔하게 지어도 자식과 부모간에 어쩔 수 없는 의존성을 만든다.



11. 합성과 유연한 설계

상속 관계 : is-a 관계, 화이트박스 재사용(내부 구현 노출)
합성 관계 : has-a 관계, 블랙박스 재사용(interface로 감싸짐)
코드 재사용을 위해서는 객체 합성이 클래스 상속보다 더 좋은 방법이다.

상속이 사용되었을 때 문제점을 합성을 사용했을 때는 발생하지 않는다.

불필요한 인터페이스 상속 발생 안함
의존하는 객체의 메서드는 항상 public interface에 감싸지기에 외부 객체의 메서드 구현에 영향 받을 일이 없음.

  • 클래스 폭발(Class explosion)
    조합의 폭발(combinational explosion)이라고 불리기도 한다. 조합이 조건의 결합이고 조건이 독립적일 경우 조건의 가짓수마다 조합이 폭발적으로 증가하는 현상을 말한다.

상속을 사용시 독립적인 조건과 클래스간에 강력한 결합이 발생하기에 조건 마다 클래스를 만들어줘야 한다.

하지만 합성을 사용해 추상화된 역할 객체를 인자로 가지고 있으면 독립적인 조건과 해당 조건과 합성된 클래스는 약한 결합성이 형성되고 나중에 런타임때 구체적인 객체를 주입해주는 식으로 유연하게 만들 수 있다.

믹스인

합성은 실행 시점에 객체를 조합하는 재사용 방법이라면 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 재사용 방법이다.

믹스인은 상속 보다는 합성에 가까운 방식이다. 구체적인 객체와 의존하는 코드를 class code 바깥으로 빼내어서 class 내부는 결합도가 낮은 상태로 유지하게 해준다.

//trait
trait RateDiscountablePolicy extends BasicRatePolicy{
  val discountAmount: Money;
  
  override def calculateFee(phone: Phone):Money = {
  	super.calculateFee(phone);
    //...
  }
}

//use
class RateDiscountableRegularPolicy(amount: Money, seconds: Duration, ...)
extends RegularPolicy(amount, seconds)
with RateDiscountablePolicy;

//instance
new RegularPolicy(Money(100),...)
with RateDiscountablePolicy
with TaxablePolicy {
//...
}

mixin 되는 trait 내부 super는 합성된 객체의 public interface로 캡슐화된 연산만 제공한다. 그리고 trait의 extends 문법은 상속을 의미하는 게 아니라 결합될 수 있는 객체의 제한을 의미한다. trait 자체와 trait가 적용되는 class 사이는 public interface로 분리되어 있다.

그래서 class 코드 작성 직후 여러 trait를 조합해서 기능을 추가해도 문제가 없다. 합성이 런타임 때 실제 객체를 주입해서 기능을 조합한다면 mixin은 컴파일 타임 때 이미 만들어진 class에 trait를 결합해서 기능을 조합한다.

상속은 각각의 조건들마다 결합된 결과물들을 미리 만든다.
합성은 결합된 결과물을 미리 만드는 대신 런타임 때 의존성 주입을 통해 원하는 결과물을 조합해서 사용한다.
믹스인은 결합된 결과물을 미리 만드는 대신 코드 작성 중에 필요할 때 조건을 조합해 결과물을 만들어서 쓴다.

profile
그냥... 즐기자..

0개의 댓글