상속은 재사용할 수 있는 방법이지만 캡슐화의 측면에서 합성이 더 좋은 방법이다.
자바에서 상속과 합성(구성)은 객체 지향 프로그래밍의 두 가지 주요 개념으로, 코드 재사용을 가능하게 합니다. 상속은 하위 클래스가 상위 클래스의 속성과 메서드를 "상속"받는 메커니즘을 의미합니다. 반면에 합성은 한 객체가 다른 객체의 인스턴스를 포함하여 해당 객체의 기능을 사용할 수 있도록 하는 것입니다. 캡슐화의 관점에서 볼 때, 합성은 상속보다 더 유연한 코드 재사용 방식을 제공하며, 이에 대해 증명하는 코드를 예시로 보여드리겠습니다.
// 상속
class Vehicle {
public void start() {
System.out.println("Vehicle is starting");
}
}
class Car extends Vehicle {
@Override
public void start() {
super.start();
System.out.println("Car is starting");
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.start();
}
}
이 예에서 Car
클래스는 Vehicle
클래스에서 start
메서드를 상속받습니다. **Car
**는 start
메서드를 오버라이드하여 추가 기능을 제공합니다. 이 방법은 코드를 재사용하지만, Car
클래스는 Vehicle
클래스의 구현에 강하게 결합되어 있어, Vehicle
클래스의 변경이 Car
클래스에 영향을 미칠 수 있기 때문에 그닥 좋다고 생각되지 않을 것입니다. 이에 반해 합성을 사용한다면 ??
// 합성
interface Vehicle {
void start();
}
class Engine {
public void start() {
System.out.println("Engine is starting");
}
}
class Car implements Vehicle {
private Engine engine;
public Car() {
this.engine = new Engine();
}
@Override
public void start() {
engine.start();
System.out.println("Car is starting");
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.start();
}
}
코드는 뭔가 장황해 보이지만, 조금만 객체에 대해 공부하셨다면, 사실상 가독성이 더 좋아졌습니다.
이 예에서 Car
클래스는 Vehicle
인터페이스를 구현하고, 내부적으로 Engine
클래스의 인스턴스를 사용합니다. 이는 **Car
**가 **Engine
**의 특정 구현에 의존하지 않도록 하여 더 낮은 결합도와 더 높은 응집도를 유지합니다. 또한, Engine
클래스를 다른 클래스로 교체하거나 수정해야 할 때 Car
클래스를 변경하지 않고도 유연하게 대응할 수 있다는 장점이 있습니다.
응집도와 결합도는 캡슐화의 정도에 따라 항상 동행하는 개념이라고 보시면 됩니다.
합성은 객체 간의 관계를 더 유연하게 만들어 주며, 코드의 재사용성과 유지 보수성을 향상시킵니다. 상속은 특정 상황에서 유용할 수 있지만, 캡슐화를 유지하고 시스템의 각 부분을 독립적으로 유지 관리할 수 있는 능력 측면에서 합성이 더 우수한 접근 방식을 제공합니다. 즉, 합성이 짜세다 이말입니다 ! (매우 주관적)
그럼이데 불구하고 객체지향 패러다임에서 가장 중요한 것은 “역할, 책임, 협력”입니다.
스포하자면, 사실상 책임이 가장 중요하긴 합니다. 책임이 있어야 역할이 있고, 각 역할은 해당 기능을 안 쓰거나 하면 다른 책임의 역할에서 협력해서 돌아가는 로직이기 때문입니다.