다운 캐스팅을 피하는 방법?


12

내 질문은 슈퍼 클래스 동물의 특별한 경우에 관한 것입니다.

  1. AnimalmoveForward()eat().
  2. Seal확장 Animal합니다.
  3. Dog확장 Animal합니다.
  4. 그리고 Animal이라고 불리는 특별한 생물이 있습니다 Human.
  5. Humanspeak()의해 구현되지 않은 메소드도 구현합니다 Animal.

수락하는 추상 메소드의 구현에서 메소드 Animal를 사용하고 싶습니다 speak(). 다운 캐스트를하지 않으면 불가능한 것 같습니다. 제레미 밀러 (Jeremy Miller)는 그의 기사 에서 다운 캐스트 냄새가 난다고 썼다 .

이 상황에서 다운 캐스팅을 피하는 솔루션은 무엇입니까?


6
모델을 수정하십시오. 기존 분류 체계 계층을 사용하여 클래스 계층을 모델링하는 것은 일반적으로 잘못된 생각입니다. 추상화를 작성하지 말고 기존 코드에서 추상화를 가져온 다음 코드를 맞추십시오.
Euphoric 2016 년

2
Animals가 말하기를 원한다면 말하기 능력이 동물을 만드는 것의 일부입니다 : artima.com/interfacedesign/PreferPoly.html
JeffO

8
앞으로 움직이다? 게는 어때요?
Fabio Marcolini 2016 년

Effective Java, BTW의 항목 번호는 무엇입니까?
goldilocks 2012 년

1
어떤 사람들은 말할 수 없으므로 시도하면 예외가 발생합니다.
Tulains Córdova

답변:


12

특정 클래스가 Human무언가를하기 위해 유형인지 여부를 알아야하는 메소드가있는 경우 특히 SOLID 원칙을 위반하는 것입니다 .

  • 개방 / 폐쇄 원칙-앞으로 말을 할 수있는 새로운 동물 유형 (예 : 앵무새)을 추가하거나 해당 유형에 특정한 작업을 수행해야하는 경우 기존 코드를 변경해야합니다.
  • 인터페이스 분리 원리-너무 일반화하는 것처럼 들립니다. 동물은 광범위한 종을 포괄 할 수 있습니다.

내 의견으로는, 메소드가 특정 클래스 유형을 예상하고 특정 메소드를 호출하려면 인터페이스가 아닌 해당 클래스 만 수락하도록 메소드를 변경하십시오.

이 같은 :

public void MakeItSpeak( Human obj );

이 같은 :

public void SpeakIfHuman( Animal obj );

또한 Animal호출 에 대한 추상 메소드를 작성할 수 canSpeak있으며 각 구체적인 구현은 "말할 수 있는지"를 정의해야합니다.
Brandon

그러나 나는 당신의 대답이 더 좋습니다. 그렇지 않은 것과 같은 것을 다루려고 할 때 많은 복잡성이 발생합니다.
Brandon

@Brandon이 경우 "말할 수 없으면"예외를 던집니다. 아니면 아무것도하지 마십시오.
BЈовић

이 같은 과부하를 얻을 그래서 : public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}public void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
바트 웨버에게

1
makeItSpeak (ISpeakingAnimal animal)-ISpeakingAnimal이 Animal을 구현하도록 할 수 있습니다. ISpeakingAnimal}의 인스턴스 인 경우 makeItSpeak (Animal animal) {발화 할 수도 있지만, 희미한 냄새가납니다.
ptyx

6

문제는 다운 캐스팅하는 것이 아니라 다운 캐스트하는 것 Human입니다. 대신 인터페이스를 작성하십시오.

public interface CanSpeak{
    void speak();
}

public abstract class Animal{
    //....
}

public class Human extends Animal implements CanSpeak{
    public void speak(){
        //....
    }
}

public void mysteriousMethod(Animal animal){
    //....
    if(animal instanceof CanSpeak){
        ((CanSpeak)animal).speak();
    }else{
        //Throw exception or something
    }
    //....
}

이런 식으로 조건은 동물 Human이 아니라는 것입니다. 조건은 말을 할 수 있다는 것입니다. 이 수단 mysteriousMethod의 다른 사람이 아닌 서브 클래스로 작업 할 수 있습니다 Animal만큼 그들은 구현으로 CanSpeak.


이 경우 Animal 대신 CanSpeak 인스턴스를 인수로 사용해야합니다.
Newtopian

1
@Newtopian 그 메소드는에서 어떤 것도 필요하지 않다고 가정하고, 그 메소드의 Animal모든 사용자는 CanSpeak타입 레퍼런스 (또는 Human타입 레퍼런스) 를 통해 보내려는 객체를 보유한다고 가정합니다 . 이 경우 해당 방법이 Human처음에 사용되었을 수 있으므로 소개 할 필요가 없습니다 CanSpeak.
Idan Arye

인터페이스를 제거하는 것은 메소드 서명에 구체적으로 연결되어 있지 않습니다. 사람 만 있고 "CanSpeak"인 사람 만있는 경우에만 인터페이스를 제거 할 수 있습니다.
Newtopian

1
@Newtopian 우리가 처음 소개 한 이유는 CanSpeak그것을 구현하는 것이 Human아니라 그것을 사용하는 것 (메서드)이기 때문입니다. 요점은 CanSpeak해당 클래스를 구체적인 클래스에서 분리하는 것입니다 Human. 우리가 CanSpeak다르게 취급하는 방법이 없다면, 그것을 구별하는 데 아무런 의미가 없습니다 CanSpeak. 우리는 단지 메소드가 있기 때문에 인터페이스를 만들지 않습니다.
Idan Arye

2

동물에게 대화를 추가 할 수 있습니다. 개 짖는 소리, 인간은 말한다.

그러나 메서드가 if (Animal is Human) Speak ()로 설계된 것처럼 들립니다.

당신이 묻고 싶은 질문은 대안이 무엇입니까? 내가 원하는 것을 정확히 알지 못하기 때문에 제안을하기가 어렵습니다. 다운 캐스팅 / 업 캐스팅이 최선의 방법 인 이론적 인 상황이 있습니다.


15
봉인은 아야 아야 하지만 여우는 정의되어 있지 않습니다.

2

이 경우 클래스 speak()에서 기본 구현은 다음 과 AbstractAnimal같습니다.

void speak() throws CantSpeakException {
  throw new CantSpeakException();
}

이때 Abstract 클래스에는 기본 구현이 있으며 올바르게 작동합니다.

try {
  thingy.speak();
} catch (CantSeakException e) {
  System.out.println("You can't talk to the " + thingy.name());
}

예, 이것은 코드를 통해 흩어져있는 try-catches가 있음을 의미 speak하지만 if(thingy is Human)대신 모든 스포크를 래핑하는 것입니다.

예외의 장점은 어떤 시점에서 (앵무새)라고 말하는 다른 유형의 물건이 있으면 모든 테스트를 다시 구현할 필요가 없다는 것입니다.


1
나는 이것이 파이썬 코드가 아닌 한 예외를 사용해야하는 좋은 이유라고 생각하지 않습니다.
Bryan Chen

1
이것이 기술적으로 효과가 있기 때문에 투표하지는 않지만 실제로는 이런 종류의 디자인이 마음에 들지 않습니다. 부모 구현할 수없는 부모 수준에서 메서드를 정의해야하는 이유는 무엇 입니까? 객체가 어떤 유형인지 알지 못하면 (이러한 문제가 발생하지 않은 경우) 항상 피할 수있는 예외를 확인하기 위해 try / catch를 사용해야합니다. 당신은 canSpeak()이것을 더 잘 처리 하는 방법을 사용할 수도 있습니다 .
Brandon

2
나는 구식 구현을 사용하고 있기 때문에 예외를 던지기 위해 많은 작업 메소드를 다시 작성하려는 누군가의 결정에서 비롯된 수많은 결함이있는 프로젝트에서 일했습니다. 그는 구현을 수정했지만 대신 모든 곳에서 예외를 처리하도록 선택했습니다. 당연히 수백 개의 버그로 QA에 들어갔습니다. 따라서 호출되지 않아야하는 메소드에서 예외를 던지는 것에 대해 편견이 있습니다. 호출되지 않아야하는 경우 삭제하십시오 .
Brandon

2
이 경우 예외를 던지는 대안은 아무것도하지 않을 수 있습니다. 결국, 그들은 말할 수 없습니다 :)
BЈовић

@Brandon은 처음부터 그것을 디자인하는 것과 나중에 그것을 개조하는 것 사이에 차이가 있습니다. Java8에서 기본 메소드가있는 인터페이스를 보거나 예외를 던지지 않고 조용히 할 수도 있습니다. 그러나 중요한 점은 다운 캐스트 할 필요가 없도록 전달되는 유형으로 함수를 정의해야한다는 것입니다.

1

다운 캐스팅은 때때로 필요하고 적절합니다. 특히, 능력이 있거나없는 물건이있는 경우에 적합하며, 능력이없는 물건을 기본 방식으로 처리하는 동안 존재하는 경우에는 그 능력을 사용하려고합니다. 간단한 예로서, a String가 다른 임의의 객체와 같은지 물었다 고 가정하자 . 하나 String가 다른 것과 같기 String위해서는 다른 문자열의 길이와 배경 문자 배열을 검사해야합니다. 그러나 a String가 a 와 같은지 물으면 A Dog의 길이에 액세스 할 수 없지만 Dog반드시 그럴 필요는 없습니다. 대신, a String자체를 비교해야하는 객체가String비교는 기본 동작을 사용해야합니다 (다른 객체가 같지 않다고보고).

다운 캐스팅이 가장 모호한 것으로 간주되는 시간은 캐스팅중인 오브젝트가 올바른 유형 인 것으로 "알려질"때입니다. 일반적으로 객체가로 알려진 경우 Cat유형 변수 Cat대신 유형 변수를 사용 Animal하여 참조해야합니다. 그러나 이것이 항상 작동하지 않는 경우가 있습니다. 예를 들어, Zoo컬렉션은 짝수 / 홀수 배열 슬롯에 객체 쌍을 보유 할 수 있으며, 각 쌍의 객체는 다른 쌍으로 객체에 작용할 수없는 경우에도 서로 작용할 수있을 것으로 기대합니다. 이러한 경우 각 쌍의 객체는 구문 상 다른 쌍으로부터 객체를 전달할 수 있도록 비 특정 매개 변수 유형을 계속 수용해야합니다 . 따라서, 경우에도 CatplayWith(Animal other)방법은 경우에만 작동합니다 other을했다 Cat, (가) Zoo그에게의 요소를 통과 할 수 있어야합니다 Animal[]그것의 매개 변수 유형이 될 것이다, 그래서 Animal보다는 Cat.

다운 캐스팅이 합법적으로 피할 수없는 경우에는, 파업없이 사용해야합니다. 중요한 질문은 다운 캐스팅을 현명하게 피할 수있는시기를 결정하고, 가능한 경우에는이를 피하는 것입니다.


문자열의 경우 메소드가 있어야합니다 Object.equalToString(String string). 그러면 boolean String.equal(Object object) { return object.equalStoString(this); }다운 캐스트가 필요하지 않습니다. 동적 디스패치를 ​​사용할 수 있습니다.
Giorgio

@Giorgio : 동적 디스패치는 그 용도가 있지만 일반적으로 다운 캐스팅보다 더 나쁩니다.
supercat

동적 디스 패칭은 일반적으로 다운 캐스팅보다 더 나쁩니 까? 나는 그래도 다른 방법이다
Bryan Chen

@BryanChen : 용어에 따라 다릅니다. 나는 생각하지 않는다 Object어떤이 equalStoString가상 방법을, 나는 인용 예도 자바에서 작동하지만 C #을 (가상 파견 별개로) 동적 파견에 컴파일러가 기본적으로 가지고 것을 의미 할 방법을 모르는 I를 인정한다 클래스에서 메소드를 처음 사용할 때 리플렉션 기반 이름 검색을 수행합니다. 가상 디스패치 (유효한 메소드 주소를 포함해야하는 가상 메소드 테이블의 슬롯을 통해 단순히 호출 함)와는 다릅니다.
supercat

모델링 관점에서는 일반적으로 동적 디스패치를 ​​선호합니다. 또한 입력 매개 변수의 유형에 따라 프로 시저를 선택하는 객체 지향 방식입니다.
Giorgio

1

동물을 받아들이는 추상 메소드의 구현에서 speak () 메소드를 사용하고 싶습니다.

몇 가지 선택 사항이 있습니다.

  • 리플렉션을 사용하여 호출 speak합니다. 장점 :에 의존하지 않습니다 Human. 단점 : 이제 "말하다"라는 이름에 숨겨진 의존성이 있습니다.

  • 새로운 인터페이스를 소개하고 인터페이스 Speaker로 다운 캐스트하십시오. 특정 콘크리트 유형에 비해 유연성이 뛰어납니다. Human구현 하려면 수정해야한다는 단점이 있습니다 Speaker. 수정할 수 없으면 작동하지 않습니다Human

  • 로 다운 캐스트 Human. 다른 서브 클래스가 발언 할 때마다 코드를 수정해야한다는 단점이 있습니다. 반복적으로 돌아가거나 이전 코드를 변경하지 않고 코드를 추가하여 응용 프로그램을 확장하는 것이 이상적입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.