Java

객체지향에서 상속과 합성의 장단점

99duuk 2025. 2. 4. 17:27

oop에서는 코드 재사용성과 확장성을 높이기 위해 상속(Inheritance)합성(Composition)을 사용한다.


상속 사용을 지양하는 이유

새는 날 수 있다. 하지만 타조와 같이 날지 못하는 명확한 예외가 존재한다. 날지 못하는 새는 다시 수영할 수 있는 새와 못하는 새로 또다른 예외로 분리된다.
이런 예외는 상속 관계를 깊고 복잡하게 만든다.
    => 코드의 가독성과 유지보수성이 낮아진다. (깊고 복잡한데 상위 하나 수정하면 하위에 모두 영향..)

 

이런 문제는 합성, 인터페이스, 위임(delegation)으로 해결할 수 있다.
    - is-a 관계는 합성과 인터페이스의 has-a 관계로 대체될 수 있다.
    - 다형성은 인터페이스를 사용해 달성될 수 있다.
    - 코드 재사용은 합성과 위임으로 달성할 수 있다.

상속을 덜 사용하고 합성 사용을 권장하지만, 합성이 언제나 옳지는 않다.

상속 관계를 합성 기반으로 다시 작성한다. ~= 세분화된 분할
    = 더 많은 클래스와 인터페이스 정의가 필요하다.
     ==> 클래스와 인터페이스의 수는 코드의 복잡성과 유지 관리 비용을 증가시킨다.

클래스 간의 상속 구조가 안정적이어서 쉽게 변경되지 않고 상속 관계가 2단계 이하로 비교적 얕아 상속 관계가 복잡하지 않다면 과감하게 상속을 사용할 수 있다.
반대로 시스템이 불안정하고 상속 계층이 깊고 상속 관계가 복잡하다면 상속 대신 합성을 사용해야 한다. p114

합성은 완벽하지 않고, 상속이 항상 쓸모없는 것은 아니다.

일부 특수한 상황에서는 상속을 사용해야할 수 있다.
파라미터 유형을 변경할 수 없고 파라미터가 인터페이스가 아닌 경우(기본자료형, 구체적 클래스)


1. 상속(Inheritance)

부모 클래스(슈퍼클래스)의 기능을 자식 클래스(서브클래스)가 물려 받아 사용하는 개념

장점

  1. 코드 재사용
    부모 클래스의 코드를 그대로 사용 => 중복 감소
    자식 클래스에서 기존 메서드 수정하거나 추가만 하면 됨
  2. 일관된 인터페이스
    부모 클래스 기반으로 여러 자식 클래스가 동일한 인터페이스(메서드) 유지 가능
    다형성을 활용해 동일한 메서드를 여러 방식으로 구현 가능
  3. 객체 관계가 명확
    is-a관계 (예: dog는 animal이다)를 표현할 때 직관적임
    개념적으로 상속 계층이 분명한 경우 (Student는 Person이다.)

단점

  1. 강한 결합
    부모 클래스의 변경이 자식 클래스에 영향 미침
    부모 클래스를 수정하면 모든 자식 클래스가 영향 받을 수 있음
  2. 유연성 감소
    상속 받으면 부모 클래스의 모든 기능을 물려받으므로 불필요한 기능까지 포함될 수 있음
    특정 기능만 재사용하고 싶어도 상속을 받으면 원하지 않는 속성과 메서드까지 따라옴
  3. 다중 상속
    자바는 다중 상속 지원 않음(인터페이스는 다중 구현 가능)

2. 합성(Compositon)

다른 클래스를 멤버 변수(속성)로 포함해 기능을 재사용하는 방식

장점

  1. 낮은 결합도 (Loose Coupling)
    부모 - 자식 관계가 아닌 독립적인 객체 관계 유지 가능
    필요할 때만 특정 객체 포함해서 사용하면 됨
  2. 유연한 설계
    필요할 때 객체를 바꿔 끼울 수 있음(런타임 객체 변경 가능)
    has-a관계(예: Car는 Engine을 가짐) 표현에 적합
  3. 다중 상속 문제 없음
    여러 클래스 조합할 수 있어 다중 상속 문제 없이 기능 활용 가능
    객체간 의존성 줄이고 유지보수 쉬워짐 (개별적인 변경 가능. 독립적)

단점

  1. 코드 작성량 증가
    단순한 상속보다 구현해야할 코드가 더 많아질 수도 있음
    내부적으로 위임하는 방식이므로 인터페이스 구현이 필요
  2. 클래스의 역할 직관적이지 않을 수 있음
    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 (비정상적인 동작)
    }
}
  • DogAnimal을 상속받았지만, 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...
    }
}
  • ElectricCarEngineBattery 객체를 포함하여 다중 기능을 활용 가능.

'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