[java] 상속과 구성의 차이점

구성과 상속이 동일합니까? 컴포지션 패턴을 구현하려면 Java로 어떻게 할 수 있습니까?



답변

그들은 절대적으로 다릅니다. 상속은 “is-a” 관계입니다. 작곡은 “has-a” 입니다.

C확장하는 대신 다른 클래스의 인스턴스를 클래스 의 필드로 사용하여 작성합니다 C. 컴포지션이 상속보다 훨씬 더 나은 좋은 예는 java.util.Stack현재 확장됩니다 java.util.Vector. 이것은 이제 실수로 간주됩니다. 스택 “is-not-a” 벡터; 요소를 임의로 삽입하거나 제거해서는 안됩니다. 대신 구성이되어야합니다.

불행히도 상속 계층 구조를 변경하면 기존 코드와의 호환성이 손상되므로이 디자인 실수를 수정하기에는 너무 늦습니다. 있었다 Stack상속 대신 사용 조성, 항상 API를 위반하지 않고 다른 데이터 구조를 사용하도록 수정 될 수있다 .

Josh Bloch의 책 Effective Java 2nd Edition을 강력히 추천합니다.

  • 항목 16 : 상속보다 구성을 선호하라
  • 항목 17 : 상속을위한 디자인 및 문서화

좋은 객체 지향 디자인은 기존 클래스를 자유롭게 확장하는 것이 아닙니다. 첫 번째 본능은 대신 작성하는 것입니다.


또한보십시오:


답변

구성 수단 HAS A
상속 수단IS A

Example: 자동차 에는 엔진이 있고 자동차 자동차입니다

프로그래밍에서 이것은 다음과 같이 표현됩니다.

class Engine {} // The Engine class.

class Automobile {} // Automobile class which is parent to Car class.

class Car extends Automobile { // Car is an Automobile, so Car class extends Automobile class.
  private Engine engine; // Car has an Engine so, Car class has an instance of Engine class as its member.
}


답변

상속은 어떻게 위험 할 수 있습니까?

예를 들어 보자

public class X{
   public void do(){
   }
}
Public Class Y extends X{
   public void work(){
       do();
   }
}

1) 위 코드에서 알 수 있듯이 클래스 Y는 클래스 X와 매우 강한 결합을 갖습니다. 수퍼 클래스 X에서 변경된 사항이 있으면 Y가 급격히 중단 될 수 있습니다. 미래에 클래스 X가 아래 서명으로 메소드 작업을 구현한다고 가정하십시오.

public int work(){
}

클래스 X에서 변경이 수행되지만 클래스 Y를 컴파일 할 수 없게됩니다. 따라서 이러한 종류의 종속성은 모든 수준으로 올라갈 수 있으며 매우 위험 할 수 있습니다. 수퍼 클래스가 모든 서브 클래스 내부의 코드에 대한 완전한 가시성을 갖지 못할 때마다 서브 클래스는 수퍼 클래스에서 일어나는 일을 항상 인식 할 수 있습니다. 따라서이 강력하고 불필요한 커플 링을 피해야합니다.

구성이이 문제를 어떻게 해결합니까?

같은 예제를 수정하여 볼 수 있습니다

public class X{
    public void do(){
    }
}

Public Class Y{
    X x = new X();
    public void work(){
        x.do();
    }
}

여기서는 Y 클래스에서 X 클래스의 참조를 작성하고 X 클래스의 인스턴스를 작성하여 X 클래스의 메소드를 호출합니다. 이제 강력한 결합이 사라졌습니다. 슈퍼 클래스와 서브 클래스는 서로 매우 독립적입니다. 클래스는 상속 상황에서 위험한 변경을 자유롭게 할 수 있습니다.

2) 컴포지션 호출 유연성을 제공한다는 점에서 컴포지션의 두 번째 장점은 다음과 같습니다.

class X implements R
{}
class Y implements R
{}

public class Test{
    R r;
}

r 참조를 사용하는 테스트 클래스에서 Y 클래스뿐만 아니라 X 클래스의 메소드를 호출 할 수 있습니다. 이 유연성은 상속에 없었습니다

3) 또 다른 큰 장점 : 단위 테스트

public class X {
    public void do(){
    }
}

Public Class Y {
    X x = new X();
    public void work(){
        x.do();
    }
}

위의 예에서 x 인스턴스의 상태를 알 수없는 경우 일부 테스트 데이터를 사용하여 쉽게 조롱 할 수 있으며 모든 방법을 쉽게 테스트 할 수 있습니다. 인스턴스의 상태를 얻고 메소드를 실행하기 위해 수퍼 클래스에 크게 의존했기 때문에 이것은 전혀 상속이 불가능했습니다.

4) 상속을 피해야하는 또 다른 이유는 Java가 다중 상속을 지원하지 않기 때문입니다.

이것을 이해하기 위해 예제를 보자.

Public class Transaction {
    Banking b;
    public static void main(String a[])
    {
        b = new Deposit();
        if(b.deposit()){
            b = new Credit();
            c.credit();
        }
    }
}

알아 둘만 한 :

  1. 상속은 컴파일 타임에 기능을 제공하는 동안 런타임에 쉽게 달성됩니다.

  2. 구성은 HAS-A 관계라고도하며 상속은 IS-A 관계라고도합니다.

따라서 위의 여러 가지 이유로 항상 상속보다 구성을 선호하는 습관을들이십시오.


답변

@Michael Rodrigues의 답변이 정확하지 않으며 (죄송합니다. 직접 의견을 말할 수 없습니다) 약간의 혼란을 초래할 수 있습니다.

인터페이스 구현 은 상속의 한 형태입니다. 인터페이스를 구현할 때 모든 상수를 상속 할뿐만 아니라 객체가 인터페이스에 의해 지정된 유형이되도록 커밋하고 있습니다. 여전히 ” is-a “관계입니다. 자동차가 Fillable을 구현 하면 자동차 Fillable이며 , Fillable을 사용하는 곳이라면 어디에서나 코드에 사용할 수 있습니다 .

구성은 상속과 근본적으로 다릅니다. 컴포지션을 사용할 때 상속을 사용할 때 만드는 ” is-a “관계 와 반대로 (다른 답변 에서 볼 수 있듯이) 두 객체간에 ” has-a “관계 를 만드는 것 입니다.

따라서 다른 질문의 자동차 예에서 자동차 “가스 탱크 가 있습니다 “라고 말하고 싶다면 다음과 같이 구성을 사용합니다.

public class Car {

private GasTank myCarsGasTank;

}

잘하면 그것은 오해를 해결합니다.


답변

상속IS-A 관계를 이끌어냅니다 . 구성HAS-A 관계를 가져옵니다 . 전략 패턴은 컴포지션이 특정 동작을 정의하는 알고리즘 계열이있는 경우에 사용해야한다고 설명합니다.
비행 행동을 구현하는 오리 클래스의 전형적인 예.

public interface Flyable{
 public void fly();
}

public class Duck {
 Flyable fly;

 public Duck(){
  fly = new BackwardFlying();
 }
}

따라서 비행을 구현하는 여러 클래스를 가질 수 있습니다.

public class BackwardFlying implements Flyable{
  public void fly(){
    Systemout.println("Flies backward ");
  }
}
public class FastFlying implements Flyable{
  public void fly(){
    Systemout.println("Flies 100 miles/sec");
  }
}

상속을 위해서라면, 파리 기능을 반복해서 구현하는 두 종류의 새가있을 것입니다. 따라서 상속과 구성은 완전히 다릅니다.


답변

작곡은 소리 그대로입니다. 부품을 연결하여 객체를 만듭니다.

이 답변의 나머지 부분은 다음 전제에 따라 잘못 수정되었습니다 .
이것은 인터페이스로 수행됩니다.
예를 Car들어 위 의 예를 사용하면

Car implements iDrivable, iUsesFuel, iProtectsOccupants
Motorbike implements iDrivable, iUsesFuel, iShortcutThroughTraffic
House implements iProtectsOccupants
Generator implements iUsesFuel

따라서 몇 가지 표준 이론적 구성 요소를 사용하면 객체를 만들 수 있습니다. 그런 다음 House보호자가 탑승자를 보호 하는 방법과 보호자가 탑승자를 보호 하는 방법을 작성하는 것이 귀하의 임무 Car입니다.

상속은 다른 방법과 같습니다. 완전한 (또는 반 완전한) 객체로 시작하여 변경하려는 다양한 비트를 바꾸거나 재정의합니다.

예를 들어, 방법 및 방법 MotorVehicle이 제공 될 수 있습니다 . 모터 사이클과 자동차를 채우는 것과 동일하기 때문에 연료 방법을 그대로 둘 수 있지만 모터 사이클 이과 다르게 구동되므로이 방법을 무시할 수 있습니다 .FuelableDriveDriveCar

상속을 통해 일부 클래스는 이미 완전히 구현되었으며 다른 클래스에는 사용자가 재정의해야하는 메서드가 있습니다. 작곡으로 당신에게 아무것도주지 않습니다. (하지만 주위에 무언가가있는 경우 다른 클래스에서 메소드를 호출하여 인터페이스를 구현할 수 있습니다).

컴포지션은 iUsesFuel과 같은 방법을 사용하는 경우 자동차 여부에 관계없이 연료를 공급할 수있는 물체를 다루는 것에 대해 걱정하는 다른 방법 (다른 클래스, 다른 프로젝트)을 가질 수 있기 때문에 더 유연하게 보입니다. 보트, 스토브, 바베큐 등. 인터페이스는 인터페이스를 구현한다고 말하는 클래스가 실제로 해당 인터페이스에 관한 모든 메소드를 갖도록 요구합니다. 예를 들어

iFuelable Interface:
   void AddSomeFuel()
   void UseSomeFuel()
   int  percentageFull()

다른 방법을 사용할 수 있습니다

private void FillHerUp(iFuelable : objectToFill) {

   Do while (objectToFill.percentageFull() <= 100)  {

        objectToFill.AddSomeFuel();
   }

이상한 예제이지만 객체가 구현하기 때문에이 메소드가 채우는 것을 신경 쓰지 않는다는 것을 보여줍니다 iUsesFuel. 이야기의 끝.

대신 상속을 사용하는 경우, 당신은 다른 필요 FillHerUp를 다루는 방법을 MotorVehicles하고 Barbecues당신이 어떤에서 상속에 다소 이상한 “ObjectThatUsesFuel”기본 객체를 가지고하지 않는 한,.


답변

구성과 상속이 동일합니까?

그들은 동일하지 않습니다.

구성 : 개체 그룹을 개체의 단일 인스턴스와 동일한 방식으로 처리해야합니다. 컴포지트의 목적은 개체를 트리 구조로 “구성하여” 전체 계층 구조를 나타냅니다.

상속 : 클래스는 직접적이든 간접적이든 상관없이 모든 수퍼 클래스에서 필드와 메소드를 상속합니다. 서브 클래스는 상속하는 메소드를 대체하거나 상속 된 필드 나 메소드를 숨길 수 있습니다.

컴포지션 패턴을 구현하려면 Java로 어떻게 할 수 있습니까?

Wikipedia 기사는 Java에서 복합 패턴을 구현하기에 충분합니다.

여기에 이미지 설명을 입력하십시오

주요 참가자 :

구성 요소 :

  1. 복합 부품을 포함한 모든 부품에 대한 추상화
  2. 컴포지션의 객체에 대한 인터페이스를 선언합니다.

:

  1. 컴포지션의 리프 개체를 나타냅니다.
  2. 모든 컴포넌트 메소드를 구현합니다

복합 :

  1. 복합 컴포넌트 (자식이있는 컴포넌트)를 나타냅니다.
  2. 어린이를 조작하는 방법을 구현
  3. 일반적으로 자식에게 위임하여 모든 구성 요소 메서드를 구현합니다.

복합 패턴 을 이해하기위한 코드 예제 :

import java.util.List;
import java.util.ArrayList;

interface Part{
    public double getPrice();
    public String getName();
}
class Engine implements Part{
    String name;
    double price;
    public Engine(String name,double price){
        this.name = name;
        this.price = price;
    }
    public double getPrice(){
        return price;
    }
    public String getName(){
        return name;
    }
}
class Trunk implements Part{
    String name;
    double price;
    public Trunk(String name,double price){
        this.name = name;
        this.price = price;
    }
    public double getPrice(){
        return price;
    }
    public String getName(){
        return name;
    }
}
class Body implements Part{
    String name;
    double price;
    public Body(String name,double price){
        this.name = name;
        this.price = price;
    }
    public double getPrice(){
        return price;
    }
    public String getName(){
        return name;
    }
}
class Car implements Part{
    List<Part> parts;
    String name;

    public Car(String name){
        this.name = name;
        parts = new ArrayList<Part>();
    }
    public void addPart(Part part){
        parts.add(part);
    }
    public String getName(){
        return name;
    }
    public String getPartNames(){
        StringBuilder sb = new StringBuilder();
        for ( Part part: parts){
            sb.append(part.getName()).append(" ");
        }
        return sb.toString();
    }
    public double getPrice(){
        double price = 0;
        for ( Part part: parts){
            price += part.getPrice();
        }
        return price;
    }
}

public class CompositeDemo{
    public static void main(String args[]){
        Part engine = new Engine("DiselEngine",15000);
        Part trunk = new Trunk("Trunk",10000);
        Part body = new Body("Body",12000);

        Car car = new Car("Innova");
        car.addPart(engine);
        car.addPart(trunk);
        car.addPart(body);

        double price = car.getPrice();

        System.out.println("Car name:"+car.getName());
        System.out.println("Car parts:"+car.getPartNames());
        System.out.println("Car price:"+car.getPrice());
    }

}

산출:

Car name:Innova
Car parts:DiselEngine Trunk Body
Car price:37000.0

설명:

  1. 부분 은 잎이다
  2. 자동차 에는 많은 부품이 포함되어 있습니다
  3. 자동차의 다른 부분 이 자동차에 추가되었습니다
  4. 의 가격 = (각의 가격의 합 )

구성 및 상속의 장단점에 대해서는 아래 질문을 참조하십시오.

상속보다 구성을 선호하십니까?