oop에서는 코드 재사용성과 확장성을 높이기 위해 상속(Inheritance)와 합성(Composition)을 사용한다.
상속 사용을 지양하는 이유
새는 날 수 있다. 하지만 타조와 같이 날지 못하는 명확한 예외가 존재한다. 날지 못하는 새는 다시 수영할 수 있는 새와 못하는 새로 또다른 예외로 분리된다.
이런 예외는 상속 관계를 깊고 복잡하게 만든다.
=> 코드의 가독성과 유지보수성이 낮아진다. (깊고 복잡한데 상위 하나 수정하면 하위에 모두 영향..)
이런 문제는 합성, 인터페이스, 위임(delegation)으로 해결할 수 있다.
- is-a 관계는 합성과 인터페이스의 has-a 관계로 대체될 수 있다.
- 다형성은 인터페이스를 사용해 달성될 수 있다.
- 코드 재사용은 합성과 위임으로 달성할 수 있다.
상속을 덜 사용하고 합성 사용을 권장하지만, 합성이 언제나 옳지는 않다.
상속 관계를 합성 기반으로 다시 작성한다. ~= 세분화된 분할
= 더 많은 클래스와 인터페이스 정의가 필요하다.
==> 클래스와 인터페이스의 수는 코드의 복잡성과 유지 관리 비용을 증가시킨다.
클래스 간의 상속 구조가 안정적이어서 쉽게 변경되지 않고 상속 관계가 2단계 이하로 비교적 얕아 상속 관계가 복잡하지 않다면 과감하게 상속을 사용할 수 있다.
반대로 시스템이 불안정하고 상속 계층이 깊고 상속 관계가 복잡하다면 상속 대신 합성을 사용해야 한다. p114
합성은 완벽하지 않고, 상속이 항상 쓸모없는 것은 아니다.
일부 특수한 상황에서는 상속을 사용해야할 수 있다.
파라미터 유형을 변경할 수 없고 파라미터가 인터페이스가 아닌 경우(기본자료형, 구체적 클래스)
1. 상속(Inheritance)
부모 클래스(슈퍼클래스)의 기능을 자식 클래스(서브클래스)가 물려 받아 사용하는 개념
장점
- 코드 재사용
부모 클래스의 코드를 그대로 사용 => 중복 감소
자식 클래스에서 기존 메서드 수정하거나 추가만 하면 됨 - 일관된 인터페이스
부모 클래스 기반으로 여러 자식 클래스가 동일한 인터페이스(메서드) 유지 가능
다형성을 활용해 동일한 메서드를 여러 방식으로 구현 가능 - 객체 관계가 명확
is-a
관계 (예: dog는 animal이다)를 표현할 때 직관적임
개념적으로 상속 계층이 분명한 경우 (Student는 Person이다.)
단점
- 강한 결합
부모 클래스의 변경이 자식 클래스에 영향 미침
부모 클래스를 수정하면 모든 자식 클래스가 영향 받을 수 있음 - 유연성 감소
상속 받으면 부모 클래스의 모든 기능을 물려받으므로 불필요한 기능까지 포함될 수 있음
특정 기능만 재사용하고 싶어도 상속을 받으면 원하지 않는 속성과 메서드까지 따라옴 - 다중 상속
자바는 다중 상속 지원 않음(인터페이스는 다중 구현 가능)
2. 합성(Compositon)
다른 클래스를 멤버 변수(속성)로 포함해 기능을 재사용하는 방식
장점
- 낮은 결합도 (Loose Coupling)
부모 - 자식 관계가 아닌 독립적인 객체 관계 유지 가능
필요할 때만 특정 객체 포함해서 사용하면 됨 - 유연한 설계
필요할 때 객체를 바꿔 끼울 수 있음(런타임 객체 변경 가능)has-a
관계(예: Car는 Engine을 가짐) 표현에 적합 - 다중 상속 문제 없음
여러 클래스 조합할 수 있어 다중 상속 문제 없이 기능 활용 가능
객체간 의존성 줄이고 유지보수 쉬워짐 (개별적인 변경 가능. 독립적)
단점
- 코드 작성량 증가
단순한 상속보다 구현해야할 코드가 더 많아질 수도 있음
내부적으로 위임하는 방식이므로 인터페이스 구현이 필요 - 클래스의 역할 직관적이지 않을 수 있음
is-a
관계가 아니라has-a
관계이므로 직관적으로 이해하기 어려울 수 있음
예를 들어, Dog extends Animal은 쉽게 이해되지만 Dog has a Leg처럼 표현하면 관계가 더 복잡해 보일 수 있음.
📌 상속(Inheritance) vs 합성(Composition) 예제 코드 (Java 기준)
각 장점과 단점에 해당하는 코드 예제를 Java로 작성했습니다.
1. 코드 재사용 (Code Reusability)
(1) 상속 - 부모 클래스 기능을 재사용
class Animal {
public String speak() {
return "Some sound";
}
}
class Dog extends Animal { // 상속
@Override
public String speak() {
return "Woof!";
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.speak()); // 출력: Woof!
}
}
Dog
클래스가Animal
의 메서드를 상속받아 재사용.- 부모 클래스의
speak()
를 오버라이딩하여 원하는 동작을 추가.
(2) 합성 - 다른 클래스를 포함해서 기능 사용
class Sound {
public String makeSound() {
return "Woof!";
}
}
class Dog {
private Sound sound; // 합성 사용
public Dog() {
this.sound = new Sound();
}
public String speak() {
return sound.makeSound();
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.speak()); // 출력: Woof!
}
}
Dog
클래스가Sound
객체를 포함하여 기능을 위임.- 필요할 때만
Sound
클래스를 사용하도록 구성.
2. 강한 결합 vs 낮은 결합 (Tight Coupling vs Loose Coupling)
(1) 상속 - 부모 클래스 변경이 자식 클래스에 영향을 줌
class Animal {
public String speak() {
return "Some sound";
}
}
class Cat extends Animal {}
class Dog extends Animal {}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
System.out.println(cat.speak()); // Some sound
System.out.println(dog.speak()); // Some sound
}
}
// 부모 클래스 변경
class Animal {
public String speak() {
return "Changed sound"; // 변경됨!
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
System.out.println(cat.speak()); // Changed sound
System.out.println(dog.speak()); // Changed sound
}
}
- 부모 클래스
Animal
이 변경되면 모든 자식 클래스(Cat
,Dog
)의 동작이 바뀜. - 결합도가 높아 유지보수가 어려울 수 있음.
(2) 합성 - 부모 클래스 변경 시 영향이 적음
class Sound {
public String makeSound() {
return "Meow";
}
}
class Cat {
private Sound sound;
public Cat(Sound sound) {
this.sound = sound; // 합성으로 객체를 주입받음
}
public String speak() {
return sound.makeSound();
}
}
public class Main {
public static void main(String[] args) {
Sound sound = new Sound();
Cat cat = new Cat(sound);
System.out.println(cat.speak()); // 출력: Meow
// Sound 클래스 변경
class Sound {
public String makeSound() {
return "Changed sound";
}
}
System.out.println(cat.speak()); // 여전히 "Meow" (객체가 독립적이므로 영향 없음)
}
}
Sound
객체를 직접 주입받아 변경이 자유롭고 결합도가 낮음.
3. 유연성 (상속은 제한적, 합성은 유연함)
(1) 상속 - 상속받으면 불필요한 기능까지 포함됨
class Animal {
public String move() {
return "I can move";
}
public String fly() {
return "I can fly";
}
}
class Dog extends Animal { } // 개는 날지 못하지만 fly()도 상속됨
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.move()); // I can move
System.out.println(dog.fly()); // I can fly (비정상적인 동작)
}
}
Dog
는Animal
을 상속받았지만,fly()
기능까지 물려받아 비정상적인 동작을 하게 됨.
(2) 합성 - 필요한 기능만 선택적으로 사용 가능
class Movement {
public String move() {
return "I can move";
}
}
class Dog {
private Movement movement; // 합성
public Dog() {
this.movement = new Movement();
}
public String move() {
return movement.move();
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.move()); // I can move
// System.out.println(dog.fly()); // 오류 발생 (필요하지 않은 기능이 없음)
}
}
Dog
클래스는Movement
기능만 가져와서 불필요한fly()
기능이 포함되지 않음.
4. 다중 기능 조합 (Inheritance vs Composition)
(1) 상속 - 다중 상속 문제 (Java에서는 불가능)
class Engine {
public String start() {
return "Engine starting...";
}
}
class Battery {
public String charge() {
return "Battery charging...";
}
}
class ElectricCar extends Engine, Battery { // Java에서는 다중 상속 불가
// 오류 발생: "Class cannot extend multiple classes"
}
- Java에서는 다중 상속을 지원하지 않음.
(2) 합성 - 여러 개의 객체를 조합 가능
class Engine {
public String start() {
return "Engine starting...";
}
}
class Battery {
public String charge() {
return "Battery charging...";
}
}
class ElectricCar {
private Engine engine;
private Battery battery;
public ElectricCar() {
this.engine = new Engine();
this.battery = new Battery();
}
public void startCar() {
System.out.println(engine.start());
System.out.println(battery.charge());
}
}
public class Main {
public static void main(String[] args) {
ElectricCar car = new ElectricCar();
car.startCar();
// 출력:
// Engine starting...
// Battery charging...
}
}
ElectricCar
는Engine
과Battery
객체를 포함하여 다중 기능을 활용 가능.
'Java' 카테고리의 다른 글
Wrapper Class (0) | 2025.02.09 |
---|---|
리플렉션 (0) | 2025.01.12 |
업캐스팅 다운캐스팅 (0) | 2025.01.07 |
LSP (0) | 2024.12.29 |
리스트처리(for -> forEach), switch (enum), Null(Optional) (1) | 2024.12.25 |