다형성
- 메시지 수발신, 다형성을 생각해보면 코드의 의존성과 실행 시점의 의존성이 다를 수 있다는 것을 알 수 있다.
- 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있다. 물론 이 간극이 달라질수록 이해하기 어려워진다.
차이에 의한 프로그래밍
- 상속에 대해서 더 자세히 살펴보면 클래스를 수정하지 않고 그냥 그대로 사용할 수 있는 가장 쉬운 방법은 상속이다.
- 부모 클래스와 다른 부분만 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법을 ‘차이에 의한 프로그래밍이라고 한다.’
- 물론 상속이 재활용에 가장 쉬운 방법일 수 있지만 단순히 ‘재활용‘만을 위해서 택한다면 큰 오해다.
- 재사용에 더 좋고 확장성이 좋은, 사이드 이펙트가 없는 방법이 있다.
상속, 인터페이스
- 인터페이스는 객체가 이해할 수 있는 메시지 목록을 정의한다.
- 상속 역시 재사용에 목표가 있을 수도 있지만 상속을 통해 자식 클래스는 자신의 인터페이스에 부모의 인터페이스를 포함하게 되고, 부모가 수신할 수 있는 메시지를 모두 수신할 수 있다.
- 이런 아이디어로 다형성이 성립하게 된다.
- 다형성이란 메시지를 수신했을 때 객체의 타입에 따라 응답할 수 있는 능력을 의미한다.
- 지연 바인딩(Lazy binding), 동적 바인딩(Dynamic binding)이라고 부른다.
- 반대로 컴파일 시점에 실행할 함수나 프로시저를 결정하는 것을 초기바인딩 또는 정적바인딩이라고 한다.
구현 상속, 인터페이스 상속
- 구현 상속(implementation inheritance)는 서브 클래싱(subclassing)이라고 하며, 순수하게 코드 재사용을 목적으로 한다.
- 인터페이스 상속(interface inheritance)는 서브 타이핑(subtyping)이라고 하며, 다형적인 협력을 위해 부모 클래스와 자식 클래스가 인터페이스를 공유할 수 있도록 상속을 이용하는 것을 의미한다.
추상화
- 구현 상속, 인터페이스 상속을 위해서는 추상화를 사용하면 된다.
- 추상화를 중심으로 코드의 구조를 설계하면 유연하고 확장 가능한 설계를 만들 수 있다.
- 설계를 통해서 구체적인 상황에 결합되는 것을 방지할 수 있기 때문이다.
- 이는 컨텍스트 독립성을 통해서 유연한 설계를 할 수 있게 해준다.
- 물론 트레이드 오프가 있을 수 있다. 모든 코드에는 합당한 이유가 있어야 한다.
재사용
- 상속 대신 재사용을 위해서는 다른 방법이 더 나을 수 있다.
- Composition은 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법을 의미한다.
상속
- 상속을 재사용 목적만으로 사용하기는 위와 같은 좋은 방법이 있어서 문제가 있다.
- 더 나아가면 아래와 같은 문제들이 있다.
- 캡슐화 위반
- 설계에서의 유연성을 앗아간다.
- 결과적으로 부모 클래스 구현이 자신 클래스에게 노출되기 때문에 캡슐화가 약화되고
- 부모와 자식 간 강결합이 형성된다.
- 또한 부모-자식 관계가 컴파일 타임에 결정되므로 설계가 유연하지 않다.
합성(Composition)
- 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 합성이라고 한다.
- 합성은 두 가지 상속의 문제를 해결한다.
- 인터페이스에 정의된 메시지를 통해서만 재사용 가능하기 때문에 구현을 효과적으로 캡슐화할 수 있다.
- 의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 설계를 유연하게 한다.