상속(Inheritance)
하위 클래스가 상위 클래스의 특성을 재정의 한 것
클래스 상속을 통해 자식 클래스는 부모 클래스의 자원을 물려 받게 되며, 부모 클래스와 다른 부분만 추가하거나 재정의함으로써 기존 코드를 쉽게 확장할 수 있다.
상속 관계를 is-a 관계라고도 한다.
class Mobile {
// ...
}
class Apple extends Mobile {
// ...
}
여기서 말하는 상속이란 클래스가 인터페이스를 구현하거나 인터페이스끼리 확장하는 인터페이스 상속이 아닌
클래스가 다른 클래스를 확장하는 구현상속을 의미한다
캡슐화를 위반한다
- 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다
- 상위 클래스의 내부 구현이 달라지면 하위 클래스를 고쳐야할 수 있다
설계가 유연하지 못하다
- 컴파일 시점에 객체의 Type이 정해진다
다중상속이 불가능하다
-다른 클래스를 상속받고 있다면 추가적으로 상속을 받을 수 없다
컴포지션(Composition)
기존 클래스가 새로운 클래스의 구성요소가 되는것
상속처럼 기존의 클래스를 확장(extend)하는 것이 아닌, 새로운 클래스를 생성하여 private 필드로 기존 클래스의 인스턴스를 참조하는 방식이다.
Composition은 콩글리쉬로 컴포지션, 또 다른 말로 조합이나 합성이라고 불린다.
컴포지션 관계를 has-a 관계라고도 한다.
class Car {
Engine engine; // 필드로 Engine 클래스 변수를 갖는다(has)
Car(Engine engine) {
this.engine = engine; // 생성자 초기화 할때 클래스 필드의 값을 정하게 됨
}
void drive() {
System.out.printf("%s엔진으로 드라이브~\n", engine.EngineType);
}
void breaks() {
System.out.printf("%s엔진으로 브레이크~\n", engine.EngineType);
}
}
class Engine {
String EngineType; // 디젤, 가솔린, 전기
Engine(String type) {
EngineType = type;
}
}
public class Main {
public static void main(String[] args) {
Car digelCar = new Car(new Engine("디젤"));
digelCar.drive(); // 디젤엔진으로 드라이브~
Car electroCar = new Car(new Engine("전기"));
electroCar.drive(); // 전기엔진으로 드라이브~
}
}
위 코드에서 볼 수 있듯이, 마치 new 생성자에 new 생성자를 받는 형식 new Car(new Engine("디젤")) 으로 쓰여진다.
Car 클래스가 Engine 클래스의 기능이 필요하다고 해서 무조건 상속하는 것이 아니라, 따로 클래스 인스턴스 변수에 저장하여 가져다 쓴다는 원리이다.
이 방식을 포워딩(forwarding)이라고 하며
필드의 인스턴스를 참조해 사용하는 메소드를 포워딩 메소드(forwarding method)라고 한다.
상속보다는 컴포지션!
상속을 지양하고 컴포지션을 지향하자.
상속은 LSP 원칙에 따라 IS-A 관계가 반드시 성립할 때만 사용해야 한다.
하지만 현실적으로 명확하게 IS-A 관계를 보장할 수 없는 경우가 대부분이다.
=> 컴포지션 기법을 사용하는 것이 객체지향의 원칙을 위배하지 않으면서 유연한 설계가 가능하다.