문제

OO 프로그래밍의 일반적인 문제는 다이아몬드 문제입니다. 두 개의 하위 클래스 B가있는 부모 클래스 A와 C가 있습니다. 이제 나는 하위 클래스 D를 가지고 있는데, B의 상속 그리고 C. 다이아몬드 문제는 이제 어떤 구현이 사용되어야하는지, B 또는 C 중 하나를 사용해야합니까?

사람들은 Java가 다이아몬드 문제를 모른다고 주장합니다. 인터페이스와 여러 개의 상속을 가질 수 있으며 구현이 없기 때문에 다이아몬드 문제가 없습니다. 이것이 정말로 사실입니까? 나는 그렇게 생각하지 않습니다. 아래를 참조하십시오 :

제거 된 차량 예

다이아몬드 문제는 항상 나쁜 클래스 디자인의 원인이며 프로그래머 나 컴파일러가 처음에는 존재하지 않아야하기 때문에 해결할 필요가 없습니까?


업데이트 : 아마도 내 예제가 제대로 선택되지 않았을 것입니다.

이 이미지를 참조하십시오

Diamond Problem
(원천: Suffolk.edu)

물론 C ++에서 사람을 가상으로 만들 수 있으므로 메모리에 한 명의 인스턴스 만있게되지만 실제 문제는 IMHO입니다. GradTeachingFellow를 위해 GetDepartment ()를 어떻게 구현 하시겠습니까? 그는 한 부서의 학생 일 수 있고 다른 부서에서 가르 칠 수 있다고 생각하십시오. 따라서 한 부서 또는 다른 부서를 반환 할 수 있습니다. 문제에 대한 완벽한 해결책은 없으며 구현이 상속되지 않을 수 있다는 사실 (예 : 학생과 교사 모두 인터페이스가 될 수 있음)은 나에게 문제를 해결하지 못하는 것 같습니다.

도움이 되었습니까?

해결책

당신이보고있는 것은 어떻게 위반 하는가입니다 Liskov 대체 원리 작동하고 논리적 인 객체 지향 구조를 갖기가 정말 어렵습니다.
기본적으로 (공개) 상속은 클래스의 목적을 좁히고 확장하지 않아야합니다. 이 경우, 두 가지 유형의 차량을 물려함으로써 실제로 목적을 확장하고 있으며, 눈에 띄지 않는 것처럼 작동하지 않습니다. 도로 차량보다 물 차량의 경우 이동이 매우 달라야합니다.
대신 수륙 양용 차량에 물 차량과 지상 차량 물체를 집계하고이 둘 중 어느 것이 현재 상황에 적합한 지 외부에서 결정할 수 있습니다.
또는 "차량"클래스가 불필요하게 일반적이며 두 가지 모두에 대한 별도의 인터페이스가 있다고 결정할 수 있습니다. 그것은 양서류 차량의 문제를 자체적으로 해결하지 못합니다. 두 인터페이스에서 움직임 방법을 "이동"이라고 부르면 여전히 문제가 있습니다. 따라서 상속 대신 집계를 제안합니다.

다른 팁

C# 가지다 명시 적 인터페이스 구현 이것을 부분적으로 다루기 위해. 최소한 중간 인터페이스 중 하나가있는 경우 (그 객체 ..)

그러나 아마도 발생하는 일은 양서류 차체 물체가 현재 물인지 땅에 있는지 여부를 알고 옳은 일을한다는 것입니다.

당신의 예에서 move() 에 속합니다 Vehicle 인터페이스하고 "A 지점에서 B 지점으로 이동"계약을 정의합니다.

언제 GroundVehicle 그리고 WaterVehicle 연장하다 Vehicle, 그들은이 계약을 암시 적으로 상속합니다 (유추 : List.contains 계약을 물려받습니다 Collection.contains - 다른 것을 지정했는지 상상해보십시오!).

콘크리트 일 때 AmphibianVehicle 구현 move(), 실제로 존중 해야하는 계약은입니다 Vehicle'에스. 다이아몬드가 있지만 계약은 다이아몬드의 한쪽을 고려하든 다른 쪽을 고려하든 (또는 디자인 문제라고 부릅니다) 계약은 변경되지 않습니다.

표면 개념을 구현하기 위해 "이동"의 계약이 필요한 경우이 개념을 모델링하지 않는 유형으로 정의하지 마십시오.

public interface GroundVehicle extends Vehicle {
    void ride();
}
public interface WaterVehicle extends Vehicle {
    void sail();
}

(유추: get(int)계약은 다음으로 정의됩니다 List 상호 작용. 아마도 정의 할 수 없었습니다 Collection, 컬렉션이 반드시 주문 될 필요는 없기 때문에)

또는 일반적인 인터페이스를 리팩터링하여 개념을 추가하십시오.

public interface Vehicle {
    void move(Surface s) throws UnsupportedSurfaceException;
}

여러 인터페이스를 구현할 때 볼 수있는 유일한 문제는 완전히 관련이없는 인터페이스의 두 가지 메소드가 충돌하는 경우입니다.

public interface Vehicle {
    void move();
}
public interface GraphicalComponent {
    void move(); // move the graphical component on a screen
}
// Used in a graphical program to manage a fleet of vehicles:
public class Car implements Vehicle, GraphicalComponent {
    void move() {
        // ???
    }
}

그러나 그것은 다이아몬드가 아닙니다. 거꾸로 된 삼각형과 비슷합니다.

사람들은 Java가 다이아몬드 문제를 모른다고 주장합니다. 인터페이스와 여러 개의 상속을 가질 수 있으며 구현이 없기 때문에 다이아몬드 문제가 없습니다. 이것이 정말로 사실입니까?

예, D에서 인터페이스의 구현을 제어하기 때문에 메소드 서명은 두 인터페이스 (B/C) 사이에서 동일하며 인터페이스가 구현이 없음을 보는 것은 문제가 없습니다.

Java는 모르지만 인터페이스 B와 C가 인터페이스 A에서 상속되고 클래스 D가 인터페이스 B와 C를 구현하면 클래스 D는 이동 메소드를 한 번만 구현하고 구현 해야하는 A.Move입니다. 당신이 말했듯이, 컴파일러는 이것에 아무런 문제가 없습니다.

지면 차량 및 수차를 구현하는 양서류 차량과 관련하여 제공하는 예에서 예를 들어 환경에 대한 참조를 저장하고 양서류의 이동 방법이 검사하는 환경에 표면 특성을 노출시켜 쉽게 해결할 수 있습니다. 이것을 매개 변수로 전달할 필요가 없습니다.

당신은 그것이 프로그래머가 해결해야 할 것이라는 의미에서 옳지 만 적어도 그것은 컴파일되며 '문제'가되어서는 안됩니다.

인터페이스 기반 상속에 대한 다이아몬드 문제는 없습니다.

클래스 기반 상속을 사용하면 다중 확장 클래스가 다른 방법을 구현할 수 있으므로 실제로 런타임에 사용되는 메소드에 대한 모호성이 있습니다.

인터페이스 기반 상속을 사용하면 방법이 하나의 구현이 있으므로 모호성이 없습니다.

편집 : 실제로, 슈퍼 클래스에서 초록으로 선언 된 방법에 대한 클래스 기반 상속에도 동일하게 적용됩니다.

양서류 차량 인터페이스가 있다는 것을 알고 있다면 그 지표 및 수차 물을 상속 받으십시오. Move () 메소드를 어떻게 구현합니까?

당신은 적합한 구현을 제공 할 것입니다 AmphibianVehicle에스.

만약 GroundVehicle "다르게"움직입니다 (즉, 다른 매개 변수를 사용합니다. WaterVehicle), 그 다음에 AmphibianVehicle 물에 대한 두 가지 방법, 하나는 땅에 상속됩니다. 그렇다면 이것이 불가능하다면 AmphibianVehicle 상속해서는 안됩니다 GroundVehicle 그리고 WaterVehicle.

다이아몬드 문제는 항상 나쁜 클래스 디자인의 원인이며 프로그래머 나 컴파일러가 처음에는 존재하지 않아야하기 때문에 해결할 필요가 없습니까?

클래스 디자인이 잘못된 경우, 컴파일러가 방법을 알지 못하기 때문에 클래스 디자인이 잘못되어야하는 것은 프로그래머입니다.

학생 / 교사 예제에서보고있는 문제는 단순히 데이터 모델이 잘못되었거나 최소한 불충분하다는 것입니다.

학생과 교사 수업은 각각의 이름을 동일한 이름을 사용하여 "부서"의 두 가지 개념을 혼란스럽게합니다. 이런 종류의 상속을 사용하려면 교사의 "GetTeachingDepartment"와 학생의 "GetResearchDepartment"와 같은 것을 정의해야합니다. 교사이자 학생 인 Gradstudent는 두 가지를 모두 구현합니다.

물론, 대학원의 현실을 감안할 때,이 모델조차도 충분하지 않을 것입니다.

콘크리트 다중 상속을 방지하면 문제를 컴파일러에서 프로그래머로 옮기고 있다고 생각하지 않습니다. 당신이 제공 한 예에서는 프로그래머가 사용할 구현을 컴파일러에 지정해야합니다. 컴파일러가 어느 것이 올바른지 추측 할 수있는 방법은 없습니다.

양서류 클래스의 경우 차량이 물이나 토지에 있는지 여부를 결정 하고이 결정을 사용하여 사용할 수있는 방법을 추가 할 수 있습니다. 이것은 매개 변수가없는 인터페이스를 보존합니다.

move()
{

  if (this.isOnLand())
  {
     this.moveLikeLandVehicle();
  }
  else
  {
    this.moveLikeWaterVehicle();
  }
}

이 경우, 양서류 차량을 차량의 서브 클래스 (수 차량 및 지체 형제)의 서브 클래스 인 것이 가장 유리할 것입니다. 수륙 양용 차량은 물 차량이나 육상 차량이 아니기 때문에 어쨌든 더 정확할 것입니다.

Move ()가지면이나 물에 따라 의미 적 차이가 있다면 (지면 차량 및 수차 인터페이스 대신 Move () 서명이있는 일반 차량 인터페이스를 확장하는 것입니다). 예는 실제로 제대로 설계되지 않은 API 중 하나입니다.

실제 문제는 이름 충돌이 효과적으로 우발적 일 때입니다. 예를 들어 (매우 인조):

interface Destructible
{
    void Wear();
    void Rip();
}

interface Garment
{
    void Wear();
    void Disrobe();
}

의복이되고 싶고 파괴 가능한 재킷이 있다면 (합법적으로 명명 된) 마모 방법에 대한 이름 충돌이 발생합니다.

Java는 이에 대한 해결책이 없습니다 (정적으로 입력 한 다른 여러 언어에 대해서도 마찬가지입니다). 다이아몬드 나 상속이 없어도 동적 프로그래밍 언어는 비슷한 문제를 가질 것입니다. 단지 이름 충돌 일뿐입니다 (오리 타이핑의 고유 한 문제).

.NET은 개념이 있습니다 명시 적 인터페이스 구현 이 클래스는 둘 다 서로 다른 인터페이스로 표시되는 한 동일한 이름과 서명의 두 가지 방법을 정의 할 수 있습니다. 호출 관련 관련 방법의 결정은 변수의 컴파일 시간 알려진 인터페이스를 기반으로합니다 (또는 Callee의 명시 적 선택에 의한 반영에 의한 경우)

합리적이고, 아마도 이름 충돌이 너무 어려워지고, Java가 명시 적 인터페이스 구현을 제공하지 못하는 것에 대해 사용할 수 없다고 약탈 당하지 않았 으면 문제가 실제 세계 사용에 중요한 문제가 아니라고 제안합니다.

나는 이것이 일반적인 솔루션이 아니라 특정 사례라는 것을 알고 있지만 상태를 결정하고 차량이 어떤 종류의 이동을 수행 할 것인지 결정하기 위해 추가 시스템이 필요하다고 생각합니다.

수륙 양용 차량의 경우 발신자 ( "스로틀"이라고 가정하자)는 물/접지의 상태를 알지 못하지만 "트랙션 제어"와 함께 "트랜스미션"과 같은 중간 결정 객체는 파악한 다음 적절한 매개 변수 이동 (휠) 또는 Move (Prop)로 Move ()를 호출하십시오.

문제는 실제로 존재합니다. 샘플에서 양면 차량 클래스는 다른 정보 인 표면이 필요합니다. 선호하는 솔루션은 Ampibian -vehicle 클래스에 Getter/Setter 메소드를 추가하여 표면 부재 (열거)를 변경하는 것입니다. 구현은 이제 올바른 일을 할 수 있고 클래스는 캡슐화되었습니다.

C ++에서 다이아몬드 문제를 가질 수 있지만 (다중 상속을 허용 함) Java 또는 C#에는 없습니다. 두 클래스에서 상속하는 방법은 없습니다. 콘크리트 방법 구현은 클래스에서만 이루어질 수 있기 때문에 동일한 방법 선언이있는 두 개의 인터페이스를 구현하지 않습니다.

C ++의 다이아몬드 문제는 이미 해결되었습니다 : 가상 상속 사용. 또는 더 나은 방법으로는 게으르고 필요하지 않은 경우 (또는 피할 수 없음) 상속하지 마십시오. 당신이 준 예에 관해서는, 이것은 땅이나 물에서 운전할 수있는 것이 무엇을 의미하는지 재정의함으로써 해결 될 수 있습니다. 물을 통과하는 능력이 실제로 수성 차량을 정의하거나 차량이 할 수있는 일입니까? 오히려 당신이 묘사 한 Move () 기능에는 "나와 실제로 여기에서 이동할 수 있습니까?"라고 묻는 일종의 논리가 있다고 생각합니다. a의 동등한 것 bool canMove() 현재 상태와 차량의 고유 능력에 따라 다릅니다. 그리고 그 문제를 해결하기 위해 여러 상속이 필요하지 않습니다. 가능한 내용에 따라 다른 방식으로 질문에 답하는 믹스 인을 사용하고 슈퍼 클래스를 템플릿 매개 변수로 취하여 가상 CANMOVE 기능이 상속 체인을 통해 표시됩니다.

사실, if Student 그리고 Teacher 둘 다 인터페이스이며 실제로 문제를 해결합니다. 그렇다면 인터페이스라면 getDepartment 단순히 당신에게 나타나야하는 방법입니다 GradTeachingFellow 수업. 둘 다 사실 Student 그리고 Teacher 인터페이스는 인터페이스가 전혀 충돌이 아닙니다. 구현 getDepartment 당신의 GradTeachingFellow 클래스는 다이아몬드 문제없이 두 인터페이스를 마무리합니다.

그러나 의견에서 지적했듯이 이것은 문제를 해결하지 못합니다. GradStudent 교육/존재 고마워 한 부서에서, 다른 부서에서 학생이되는 것. 캡슐화는 아마도 여기에서 원하는 것일 것입니다.

public class Student {
  String getDepartment() {
    return "Economics";
  }
}

public class Teacher {
  String getDepartment() {
    return "Computer Engineering";
  }
}

public class GradStudent {
  Student learning;
  Teacher teaching;

  public String getDepartment() {
    return leraning.getDepartment()+" and "+teaching.getDepartment(); // or some such
  }

  public String getLearningDepartment() {
    return leraning.getDepartment();
  }

  public String getTeachingDepartment() {
    return teaching.getDepartment();
  }
}

A보다 중요하지 않습니다 GradStudent 개념적으로 교사와 학생을 "가지고"하지 않습니다. 캡슐화는 여전히 갈 길입니다.

인터페이스 a {void add (); }

인터페이스 B는 {void add (); }

인터페이스 C는 {void add (); }

클래스 D는 B, C {

}

다이아몬드 문제가 아닙니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top