서브 클래스의 API를 오염 시키더라도 스스로 캐스팅하는 객체를 갖는 것이 괜찮습니까?


33

나는 기본 수업이 Base있습니다. 그것은 두 개의 서브 클래스를 가지고 Sub1Sub2. 각 서브 클래스에는 몇 가지 추가 메소드가 있습니다. 예를 들어, Sub1has Sandwich makeASandwich(Ingredients... ingredients)Sub2has가 boolean contactAliens(Frequency onFrequency)있습니다.

이러한 방법은 다른 매개 변수를 사용하고 완전히 다른 작업을 수행하기 때문에 완전히 호환되지 않으며이 문제를 해결하기 위해 다형성을 사용할 수 없습니다.

Base대부분의 기능을 제공하며 많은 Base객체 모음이 있습니다. 그러나 모든 Base객체는 a Sub1또는 a Sub2이며 때로는 어떤 객체 인지 알아야합니다.

다음을 수행하는 것은 나쁜 생각처럼 보입니다.

for (Base base : bases) {
    if (base instanceof Sub1) {
        ((Sub1) base).makeASandwich(getRandomIngredients());
        // ... etc.
    } else { // must be Sub2
        ((Sub2) base).contactAliens(getFrequency());
        // ... etc.
    }
}

그래서 나는 캐스팅하지 않고 이것을 피하는 전략을 생각해 냈습니다. Base이제 다음과 같은 방법이 있습니다.

boolean isSub1();
Sub1 asSub1();
Sub2 asSub2();

물론 Sub1이러한 방법을 다음과 같이 구현합니다.

boolean isSub1() { return true; }
Sub1 asSub1();   { return this; }
Sub2 asSub2();   { throw new IllegalStateException(); }

그리고 Sub2반대 방향으로 구현합니다.

불행하게도, 지금 Sub1Sub2자신의 API에서 이러한 방법이있다. 예를 들어 on에서이 작업을 수행 할 수 있습니다 Sub1.

/** no need to use this if object is known to be Sub1 */
@Deprecated
boolean isSub1() { return true; }

/** no need to use this if object is known to be Sub1 */
@Deprecated
Sub1 asSub1();   { return this; }

/** no need to use this if object is known to be Sub1 */
@Deprecated
Sub2 asSub2();   { throw new IllegalStateException(); }

이런 식으로 객체가 오직로 알려진 경우 Base, 이러한 메소드는 더 이상 사용되지 않으며 서브 클래스의 메소드를 호출 할 수 있도록 다른 유형으로 자신을 "캐스트"하는 데 사용될 수 있습니다. 이것은 나에게 우아해 보이지만 다른 한편으로는 클래스에서 메소드를 "제거"하는 방법으로 Deprecated 주석을 남용하고 있습니다.

때문에 Sub1인스턴스가 정말 A는 자료, 그것은 사용 상속보다는 캡슐화 할 수 있도록 감각을한다. 내가 잘하고 있는거야? 이 문제를 해결하는 더 좋은 방법이 있습니까?


12
3 개의 수업 모두 서로에 대해 알아야합니다. SUB3를 추가하면 코드 변경을 많이 포함하는 것이며, 추가 Sub10 명백히 고통스러운 것
댄 Pichelman

15
실제 코드를 제공하면 많은 도움이 될 것입니다. 특정 클래스의 클래스를 기반으로 결정을 내리는 것이 적절한 상황이 있지만 그러한 계획된 사례를 통해 자신이하고있는 일이 정당한지 판단하기 란 불가능합니다. 가치가있는 무엇이든 원하는 것은 방문자 또는 태그 된 Union 입니다.
Doval

1
죄송합니다. 간단한 예제를 제공하는 것이 더 쉬울 것이라고 생각했습니다. 어쩌면 내가하고 싶은 일의 광범위한 범위로 새로운 질문을 게시 할 것입니다.
codebreaker

12
당신은하고 재 구현 캐스팅을하고 instanceof, 입력을 많이 필요로 오류가 발생하기 쉬운, 그리고 열심히 더 서브 클래스를 추가 할 수있는 방법으로,.
user253751

5
경우 Sub1Sub2같은 의미로 사용 할 수 없다, 왜 당신은 그런로 처리합니까? 왜 '샌드위치 제작자'와 '외계인 접촉자'를 따로 추적하지 않습니까?
Pieter Witvoet

답변:


27

다른 답변 중 일부에서 제안했듯이 항상 기본 클래스에 함수를 추가하는 것이 합리적이지 않습니다. 너무 많은 특수 사례 함수를 추가하면 관련이없는 구성 요소를 서로 바인딩 할 수 있습니다.

예를 들어 Animal클래스 CatDog구성 요소 가있을 수 있습니다 . 인쇄하거나 GUI에 표시 하려면 기본 클래스에 추가 renderToGUI(...)하고 과도하게 사용하는 것이 과도 할 수 있습니다 sendToPrinter(...).

유형 검사 및 캐스트를 사용하는 사용 방식은 취약하지만 적어도 우려 사항을 구분합니다.

그러나 이러한 유형의 검사 / 캐스트를 자주하는 경우 방문자 / 이중 디스패치 패턴을 구현하는 옵션이 있습니다. 다음과 같이 보입니다.

public abstract class Base {
  ...
  abstract void visit( BaseVisitor visitor );
}

public class Sub1 extends Base {
  ...
  void visit(BaseVisitor visitor) { visitor.onSub1(this); }
}

public class Sub2 extends Base {
  ...
  void visit(BaseVisitor visitor) { visitor.onSub2(this); }
}

public interface BaseVisitor {
   void onSub1(Sub1 that);
   void onSub2(Sub2 that);
}

이제 코드는

public class ActOnBase implements BaseVisitor {
    void onSub1(Sub1 that) {
       that.makeASandwich(getRandomIngredients())
    }

    void onSub2(Sub2 that) {
       that.contactAliens(getFrequency());
    }
}

BaseVisitor visitor = new ActOnBase();
for (Base base : bases) {
    base.visit(visitor);
}

주요 이점은 서브 클래스를 추가하면 자동으로 누락 된 케이스가 아니라 컴파일 오류가 발생한다는 것입니다. 새로운 방문자 클래스도 기능을 끌어 들이기위한 훌륭한 대상이되었습니다. 예를 들어로 이동 getRandomIngredients()하는 것이 ActOnBase좋습니다.

루핑 로직을 추출 할 수도 있습니다. 예를 들어 위의 조각이

BaseVisitor.applyToArray(bases, new ActOnBase() );

Java 8의 람다와 스트리밍을 조금 더 마사지하고 사용하면

bases.stream()
     .forEach( BaseVisitor.forEach(
       Sub1 that -> that.makeASandwich(getRandomIngredients()),
       Sub2 that -> that.contactAliens(getFrequency())
     ));

어느 IMO는 당신이 얻을 수있는만큼 깔끔하고 간결합니다.

보다 완벽한 Java 8 예제는 다음과 같습니다.

public static abstract class Base {
    abstract void visit( BaseVisitor visitor );
}

public static class Sub1 extends Base {
    void visit(BaseVisitor visitor) { visitor.onSub1(this); }

    void makeASandwich() {
        System.out.println("making a sandwich");
    }
}

public static class Sub2 extends Base {
    void visit(BaseVisitor visitor) { visitor.onSub2(this); }

    void contactAliens() {
        System.out.println("contacting aliens");
    }
}

public interface BaseVisitor {
    void onSub1(Sub1 that);
    void onSub2(Sub2 that);

    static Consumer<Base> forEach(Consumer<Sub1> sub1, Consumer<Sub2> sub2) {

        return base -> {
            BaseVisitor baseVisitor = new BaseVisitor() {

                @Override
                public void onSub1(Sub1 that) {
                    sub1.accept(that);
                }

                @Override
                public void onSub2(Sub2 that) {
                    sub2.accept(that);
                }
            };
            base.visit(baseVisitor);
        };
    }
}

Collection<Base> bases = Arrays.asList(new Sub1(), new Sub2());

bases.stream()
     .forEach(BaseVisitor.forEach(
             Sub1::makeASandwich,
             Sub2::contactAliens));

+1 : 이런 종류의 것은 sum-types와 패턴 매칭을 일류 지원하는 언어에서 일반적으로 사용되며 구문 상 매우 추한 경우에도 Java에서 유효한 디자인입니다.
Mankarse

@Mankarse 약간의 구문 설탕은 그것을 추악한 것과 아주 멀리 만들 수 있습니다-예를 들어 업데이트했습니다.
Michael Anderson

가설 적으로 OP가 마음을 바꾸고를 추가하기로 결정했다고 가정 해 봅시다 Sub3. 방문자와 동일한 문제가 instanceof없습니까? 이제 onSub3방문자 를 추가해야하며 잊어 버리면 끊어집니다.
Radiodef

1
@Radiodef 유형을 직접 전환하는 것보다 방문자 패턴을 사용하는 이점은 onSub3방문자 인터페이스에 추가 한 후에는 아직 업데이트되지 않은 새 방문자를 만드는 모든 위치에서 컴파일 오류가 발생한다는 것입니다. 반대로 유형을 전환하면 런타임 오류가 발생하기 때문에 트리거하기가 더 까다로울 수 있습니다.
마이클 앤더슨

1
@FiveNine, 다른 사람에게 도움이 될 수있는 완전한 예를 답변 끝에 추가하면 행복합니다.
마이클 앤더슨

83

내 관점에서 보면 , 디자인이 잘못되었습니다 .

자연어로 번역하면 다음과 같은 말을합니다.

우리가 감안할 때 animals, 거기에 catsfish. 및에 animals공통적 인 속성이 있습니다 . 그러나 그것은 충분하지 않습니다 : 미분 몇 가지 특성이있다 에서 , 따라서 당신이 서브 클래스에 필요.catsfishcatfish

이제 운동 을 모델링하는 것을 잊었다는 문제가 있습니다 . 괜찮아. 비교적 쉽습니다.

for(Animal a : animals){
   if (a instanceof Fish) swim();
   if (a instanceof Cat) walk();
}

그러나 그것은 잘못된 디자인입니다. 올바른 방법은 다음과 같습니다.

for(Animal a : animals){
    animal.move()
}

move동물마다 다르게 행동을 공유 할 수있는 곳

이러한 방법은 다른 매개 변수를 사용하고 완전히 다른 작업을 수행하기 때문에 완전히 호환되지 않으며이 문제를 해결하기 위해 다형성을 사용할 수 없습니다.

즉, 디자인이 손상되었습니다.

나의 추천 : 리팩토링 Base, Sub1Sub2.


8
실제 코드를 제공하려면 권장 사항을 제시 할 수 있습니다.
Thomas Junk

9
@codebreaker 예제가 좋지 않다는 데 동의하면이 답변을 수락 한 다음 새 질문을 작성하는 것이 좋습니다. 다른 예를 갖기 위해 질문을 수정하지 마십시오. 그렇지 않으면 기존 답변이 의미가 없습니다.
Moby Disk

16
@ codebreaker 나는 이것이 실제 질문 에 대답함으로써 문제를 "해결"한다고 생각합니다 . 진짜 질문은 : 어떻게 내 문제를 해결합니까? 코드를 올바르게 리 팩터하십시오. 리팩터링 당신 있도록 .move()하거나 .attack()제대로 추상적 인 that부분 - 대신은 필요 .swim(), .fly(), .slide(), .hop(), .jump(), .squirm(), .phone_home(), 등을 어떻게 제대로 리팩토링합니까? 더 나은 예제가 없으면 알 수 없지만 ... 예제 코드와 자세한 내용이 다른 방법으로 제안하지 않는 한 여전히 일반적으로 정답입니다.
WernerCD

11
@codebreaker이 같은 상황에 클래스 계층 구조 리드는 다음 경우 정의 는 이해가되지 않습니다
패트릭 콜린스

7
@codebreaker Crisfole의 마지막 의견과 관련하여 도움이 될 수있는 다른 방법이 있습니다. 예를 들어, movevs fly/ run/ etc를 사용하면 한 문장으로 다음과 같이 설명 할 수 있습니다. 접근 자의 관점에서 언급 한 바와 같이 여기 주석의 실제 사례와 비슷하므로 객체의 상태를 검사하지 말고 질문해야합니다.
이즈 카타

9

사물 그룹이 있고 샌드위치를 ​​만들거나 외계인과 접촉하기를 원하는 상황을 상상하기는 조금 어렵습니다. 그러한 캐스팅을 발견하는 대부분의 경우 하나의 유형으로 작동합니다. 예를 들어 clang에서는 getAsFunction 이 목록의 각 노드마다 다른 것을 수행하지 않고 널이 아닌 값을 리턴 하는 선언에 대해 노드 세트를 필터링 합니다.

일련의 작업을 수행해야 할 수도 있고 작업을 수행하는 객체가 실제로 관련되어 있지 않을 수도 있습니다.

의 목록 대신 Base작업 목록에서 작업하십시오.

for (RandomAction action : actions)
   action.act(context);

어디에

interface RandomAction {
    void act(Context context);
} 

interface Context {
    Ingredients getRandomIngredients();
    double getFrequency();
}

적절한 경우 Base가 조치를 리턴하는 메소드를 구현하도록하거나 기본 목록의 인스턴스에서 조치를 선택해야하는 다른 방법을 사용할 수 있습니다. 클래스의 함수가 아니라 기본의 다른 속성입니다. 그렇지 않으면 Base에 act (Context) 메소드를 제공합니다)


2
부조리 한 메서드는 하위 클래스가 알려지면 클래스가 수행 할 수있는 작업의 차이점을 보여 주도록 설계되었으며 Context객체와 관련이 있으면 문제가 더 악화 된다고 생각 합니다. 나는 Object또한 메소드에 전달하고 , 캐스팅하고, 올바른 유형이기를 바라고 있습니다.
codebreaker

1
@codebreaker 당신은 정말로 당신의 질문을 명확히 할 필요가 있습니다-대부분의 시스템에서 그것들이하는 일에 관계없이 많은 함수들을 호출하는 유효한 이유가있을 것입니다; 예를 들어, 이벤트에 대한 규칙을 처리하거나 시뮬레이션의 단계를 수행합니다. 일반적으로 이러한 시스템에는 작업이 수행되는 컨텍스트가 있습니다. 'getRandomIngredients'와 'getFrequency'가 서로 관련이없는 경우 동일한 객체에 있지 않아야하며, 조치가 성분 또는 빈도의 소스를 캡처하도록하는 등 다른 접근법이 필요합니다.
Pete Kirkham

1
네 말이 맞아, 나는이 질문을 구할 수 있다고 생각하지 않는다. 나는 나쁜 예를 골 랐기 때문에 다른 예를 게시 할 것입니다. getFrequency () 및 getIngredients ()는 단지 자리 표시 자였습니다. 나는 그들이 무엇인지 모른다는 것을 더 분명하게하기위한 주장으로 "..."를 넣었을 것이다.
codebreaker

이것이 바로 정답입니다. (가) 것을 이해 Context하는 새로운 클래스, 또는 인터페이스가 될 필요가 없습니다. 일반적으로 컨텍스트는 호출자이므로 호출 형식은 action.act(this)입니다. 경우 getFrequency()getRandomIngredients()정적 방법은, 당신도 컨텍스트를 필요로하지 않을 수 있습니다. 이것이 내가 당신의 문제가 끔찍한 것처럼 들리는 작업 대기열을 구현하는 방법입니다.
QuestionC

또한 이것은 대부분 방문자 패턴 솔루션과 동일합니다. 차이점은 당신이 SUB1과 방문자 :: OnSub1 () / 방문객 : OnSub2 () 메소드를 구현하고있다 :: 행위 () / Sub2라는 :: 행위 ()
QuestionC

4

서브 클래스가 할 수있는 것을 정의하는 하나 이상의 인터페이스를 구현하고 있다면 어떨까요? 이 같은:

interface SandwichCook
{
    public void makeASandwich(String[] ingredients);
}

interface AlienRadioSignalAwarable
{
    public void contactAliens(int frequency);

}

그러면 수업은 다음과 같습니다.

class Sub1 extends Base implements SandwichCook
{
    public void makeASandwich(String[] ingredients)
    {
        //some code here
    }
}

class Sub2 extends Base implements AlienRadioSignalAwarable
{
    public void contactAliens(int frequency)
    {
        //some code here
    }
}

그리고 for-loop는 다음이 될 것입니다 :

for (Base base : bases) {
    if (base instanceof SandwichCook) {
        base.makeASandwich(getRandomIngredients());
    } else if (base instanceof AlienRadioSignalAwarable) {
        base.contactAliens(getFrequency());
    }
}

이 접근법의 두 가지 주요 장점 :

  • 캐스팅이 필요하지 않습니다
  • 각 서브 클래스가 원하는만큼 많은 인터페이스를 구현하도록하여 향후 변경에 유연성을 제공 할 수 있습니다.

추신 : 인터페이스의 이름으로 죄송합니다. 특정 순간에 더 멋진 것을 생각할 수 없었습니다 : D.


3
실제로 문제가 해결되지는 않습니다. 여전히 for 루프에서 캐스트가 필요합니다. (그렇지 않으면 OP의 예에서도 캐스트가 필요하지 않습니다).
Taemyr

2

접근 방식은 거의 가족 내에서 어떤 종류의 것입니다 경우에 좋은 하나가 될 수 있습니다 몇 가지 기준을 충족 몇 가지 인터페이스의 구현으로 직접 사용할 수 또는 해당 인터페이스의 구현을 만들 수 있습니다. 내장 컬렉션 유형은 IMHO가이 패턴의 혜택을 받았을 것입니다. 그러나 예제 목적으로 사용하지 않기 때문에 컬렉션 인터페이스를 발명합니다 BunchOfThings<T>.

일부 구현 BunchOfThings은 변경 가능합니다. 일부는 그렇지 않습니다. 많은 경우, Fred 오브젝트는 a로 사용할 수있는 것을 보유하고 BunchOfThingsFred 이외의 다른 오브젝트 는이를 수정할 수 없음을 알고 싶어 할 수 있습니다. 이 요구 사항은 두 가지 방법으로 충족 될 수 있습니다.

  1. Fred는 그것이 그것에 대한 유일한 언급을 가지고 있다는 것을 알고 있으며, 그것의 내부가 우주의 어느 곳에도 존재 BunchOfThings하지 않는다는 것을 언급하지 않습니다 BunchOfThings. 다른 사람이 BunchOfThings내부 또는 내부를 참조 하지 않으면 다른 사람이이를 수정할 수 없으므로 제약 조건이 충족됩니다.

  2. 어느 것도 BunchOfThings없으며, 외부 참조가되는 그 내부의 어느 한 어떠한 수단을 통해 수정 될 수 없다. 아무도를 수정할 수 없으면 BunchOfThings제약 조건이 충족됩니다.

제한 조건을 만족시키는 한 가지 방법은 수신 된 오브젝트를 무조건 복사하는 것입니다 (중첩 된 컴포넌트를 재귀 적으로 처리하는 것). 또 다른 방법은 수신 된 객체가 불변성을 약속하는지 여부를 테스트하고, 그렇지 않은 경우 복사본을 만들고 중첩 된 구성 요소로 수행하는 것입니다. 두 번째보다 깨끗하고 첫 번째보다 빠르기 쉬운 대안 AsImmutable은 객체가 불변의 사본을 만들도록 요청하는 방법 을 제공하는 것입니다 ( AsImmutable이를 지원하는 중첩 된 구성 요소에서 사용 ).

관련 메소드도 제공 될 수 있습니다 asDetached(코드가 객체를 수신하고 객체를 변형할지 여부를 모르는 경우에 사용 가능).이 경우 변경 가능한 객체를 새로운 변경 가능한 객체로 교체해야하지만 변경 불가능한 객체는 유지할 수 있습니다 )로서의 인 asMutable객체가 객체가 이전부터 반환 보유 할 것을 알고 어디 경우 ( asDetached가변의 개체 또는 가변 하나 공유 가능한 기준)을 위해, 즉 두 비공유 기준 asNewMutable코드가 외부를받는 곳 경우 ( 참조하고 데이터의 사본을 변경하려고한다는 것을 알고 있습니다. 수신 데이터가 변경 가능한 경우 변경 불가능한 사본을 작성하여 즉시 변경 가능 사본을 작성한 다음 포기하는 불변 사본을 작성하여 시작할 이유가 없습니다).

asXX메소드는 약간 다른 유형을 리턴 할 수 있지만 실제 역할은 리턴 된 오브젝트가 프로그램 요구를 충족시키는 것입니다.


0

좋은 디자인인지 아닌지에 대한 문제를 무시하고 그것이 좋거나 최소한 수용 가능하다고 가정하면 유형이 아닌 하위 클래스의 기능을 고려하고 싶습니다.

따라서 다음 중 하나를 수행하십시오.


기본 클래스의 일부 인스턴스가이를 수행 할 수 없다는 것을 알고 있지만 샌드위치 및 외계인의 존재에 대한 지식을 기본 클래스로 이동하십시오. 기본 클래스에서 구현하여 예외를 발생시키고 코드를 다음과 같이 변경하십시오.

if (base.canMakeASandwich()) {
    base.makeASandwich(getRandomIngredients());
    // ... etc.
} else { // can't make sandwiches, must be able to contact aliens
    base.contactAliens(getFrequency());
    // ... etc.
}

그런 다음 하나 또는 두 개의 서브 클래스가 재정의 canMakeASandwich()하고 하나만 makeASandwich()및 각각을 구현합니다 contactAliens().


구체적인 서브 클래스가 아닌 인터페이스를 사용하여 유형의 기능을 감지하십시오. 기본 클래스는 그대로두고 코드를 다음과 같이 변경하십시오.

if (base instanceof SandwichMaker) {
    ((SandwichMaker)base).makeASandwich(getRandomIngredients());
    // ... etc.
} else { // can't make sandwiches, must be able to contact aliens
    ((AlienContacter)base).contactAliens(getFrequency());
    // ... etc.
}

또는 가능하다면 (이 옵션이 자신의 스타일에 맞지 않거나 합리적인 것으로 생각되는 Java 스타일에 대해서는 무시해도됩니다) :

try {
    ((SandwichMaker)base).makeASandwich(getRandomIngredients());
} catch (ClassCastException e) {
    ((AlienContacter)base).contactAliens(getFrequency());
}

개인적으로 난 몰라 보통 때문에 부적절하게 잡기의 위험, 반 기대 예외를 잡는 후자의 스타일처럼 ClassCastException나오는 getRandomIngredients또는 makeASandwich,하지만 YMMV.


2
잡기 ClassCastException은 단지 ew입니다. 이것이 instanceof의 전체 목적입니다.
Radiodef

@Radiodef : 사실이지만, 한 가지 목적은 <잡기를 피하는 ArrayIndexOutOfBoundsException것이며, 일부 사람들도 그렇게하기로 결정합니다. 맛에 대한 회계 ;-) 기본적으로 내 "문제는"여기 선호하는 스타일은 일반적으로 잡는 것을 파이썬에서 나는 일입니다 어떤 예외하면 예외가 발생합니다 여부를 미리 테스트하는 것이 바람직하다 상관없이 사전 테스트는 어떻게 간단 할 것 . 아마도 Java에서 가능성이 언급되지 않을 수도 있습니다.
Steve Jessop

@SteveJessop»허가보다 용서를 구하는 것이 더 쉽다«는 슬로건입니다;)
Thomas Junk

0

여기에 자체 파생 클래스로 다운 캐스트하는 기본 클래스의 흥미로운 사례가 있습니다. 우리는 이것이 일반적으로 나쁘다는 것을 잘 알고 있지만, 우리가 합당한 이유를 찾았다면 이것에 대한 제약이 무엇인지 살펴보고 보자.

  1. 우리는 기본 수업의 모든 사례를 다룰 수 있습니다.
  2. 외국 파생 클래스는 새로운 사례를 추가 할 필요가 없습니다.
  3. 우리는 기본 클래스의 통제하에 전화하라는 정책에 따라 살 수 있습니다.
  4. 그러나 우리는 과학에서 기본 클래스가 아닌 메소드에 대해 파생 클래스에서 수행 할 정책이 기본 클래스가 아니라 파생 클래스의 정책이라는 것을 알고 있습니다.

4 인 경우 : 5. 파생 클래스의 정책은 항상 기본 클래스의 정책과 동일한 정치적 통제하에 있습니다.

2와 5는 모두 파생 클래스를 모두 열거 할 수 있음을 의미합니다. 즉, 외부 파생 클래스가 없어야합니다.

그러나 여기에 있습니다. 그것들이 모두 당신의 것이라면 if를 가상 메소드 호출 인 추상화로 바꾸고 (논 센스가 있더라도) if와 self-cast를 제거 할 수 있습니다. 따라서하지 마십시오. 더 나은 디자인을 사용할 수 있습니다.


-1

Sub1에서 하나를 수행하고 Sub2에서 다른 것을 수행하는 추상 클래스를 기본 클래스에 넣고 혼합 루프에서 해당 추상 메소드를 호출하십시오.

class Sub1 : Base {
    @Override void doAThing() {
        this.makeASandwich(getRandomIngredients());
    }
}

class Sub2 : Base {
    @Override void doAThing() {
        this.contactAliens(getFrequency());
    }
}

for (Base base : bases) {
    base.doAThing();
}

getRandomIngredients 및 getFrequency가 정의 된 방식에 따라 다른 변경이 필요할 수 있습니다. 그러나 솔직히 외계인과 접촉하는 수업에서 getFrequency를 정의하는 것이 더 좋은 곳 일 것입니다.

또한 asSub1 및 asSub2 메소드는 확실히 나쁜 습관입니다. 이 작업을 수행하려면 캐스팅으로 수행하십시오. 이러한 방법으로 캐스팅에서 얻을 수있는 것은 없습니다.


2
당신의 대답은 당신이 질문을 완전히 읽지 않았다는 것을 암시합니다. 다형성은 여기서 그것을 자르지 않을 것입니다. 각 Sub1과 Sub2에는 여러 가지 방법이 있으며, 이는 단지 예일 뿐이며 "doAThing"은 실제로 아무 의미가 없습니다. 내가하고있는 일과 일치하지 않습니다. 또한 마지막 문장은 유형 안전 보장은 어떻습니까?
codebreaker

@codebreaker-예제의 루프에서 수행하는 작업과 정확히 일치합니다. 그것이 의미가 없다면, 루프를 작성하지 마십시오. 방법으로 이해되지 않으면 루프 본문으로 이해되지 않습니다.
Random832

1
그리고 당신의 메소드는 캐스팅과 같은 방식으로 타입 안전을 보장합니다 : 객체가 잘못된 타입이라면 예외를 던집니다.
Random832

나는 나쁜 예를 골랐다는 것을 알고 있습니다. 문제는 서브 클래스에있는 대부분의 메소드가 변경 메소드보다 게터와 비슷하다는 것입니다. 이 클래스는 자체적으로 작업을 수행하지 않고 주로 데이터를 제공합니다. 다형성은 저에게 도움이되지 않습니다. 저는 소비자가 아닌 생산자를 다루고 있습니다. 또한 : 예외를 던지는 것은 런타임 보장이며 컴파일 타임만큼 좋지 않습니다.
codebreaker

1
내 요점은 귀하의 코드가 런타임 보증 만 제공한다는 것입니다. asSub2를 호출하기 전에 누군가가 isSub2를 확인하지 못하도록 막는 것은 없습니다.
Random832

-2

당신은 모두를 밀어 수 makeASandwichcontactAliens기본 클래스에 다음 더미 구현과이를 구현합니다. 리턴 유형 / 인수는 공통 기본 클래스로 해석 될 수 없기 때문입니다.

class Sub1 extends Base{
    Sandwich makeASandwich(Ingredients i){
        //Normal implementation
    }
    boolean contactAliens(Frequency onFrequency){
        return false;
    }
 }
class Sub2 extends Base{
    Sandwich makeASandwich(Ingredients i){
        return null;
    }
    boolean contactAliens(Frequency onFrequency){
       // normal implementation
    }
 }

방법의 계약을 의미하는 것과 결과의 상황에 대해 추론 할 수 있거나 추론 할 수없는 것과 같은 이런 종류의 일에는 명백한 단점이 있습니다. 샌드위치 등을 만들지 못했기 때문에 시도 할 때 재료가 사용되지 않습니다.


1
이 작업에 대해 생각했지만 Liskov 대체 원칙을 위반하므로 "sub1"을 입력 할 때와 같이 사용하기에 좋지 않습니다. 자동 완성 기능이 나타나면 makeASandwich는 있지만 Alien에 연락하지는 않습니다.
codebreaker

-5

당신이하는 일은 완벽하게 합법적입니다. 교과서를 일부 책에서 읽었 기 때문에 단지 반복해서 읽는 혐오 자들에게주의를 기울이지 마십시오. 도그마는 공학 분야가 없습니다.

동일한 메커니즘을 두 번 사용했으며 Java 런타임이 생각할 수있는 최소한 한 곳에서 동일한 작업을 수행 할 수 있다고 확신하면서 코드의 성능, 유용성 및 가독성을 향상시킵니다. 사용합니다.

예를 들어 java.lang.reflect.Member, 이는 java.lang.reflect.Fieldand 의 기본입니다 java.lang.reflect.Method. (실제 계층은 그것보다 조금 더 복잡하지만 관련이 없습니다.) 필드와 메소드는 크게 다른 동물입니다. 하나는 얻을 수 있거나 설정할 수있는 값을 가지고 있지만 다른 하나는 그런 것을 가지고 있지 않지만 호출 할 수는 있습니다. 다수의 매개 변수이며 값을 리턴 할 수 있습니다. 따라서 필드와 방법은 모두 멤버이지만, 그들과 함께 할 수있는 일은 샌드위치를 ​​만드는 것과 외계인을 접촉하는 것만 큼 서로 다릅니다.

이제 리플렉션을 사용하는 코드를 작성할 때 우리는 종종 Member손에 들고 있지만 a Method또는 a Field(또는 드물게 다른 것) 임을 알고 있지만 instanceof정확하게 파악하기 위해서는 모든 지루한 작업 을 수행해야합니다. 그것이 무엇인지 그리고 우리는 그것을 참조하기 위해 그것을 던져야합니다. (그리고 이것은 지루할뿐만 아니라 성능도 좋지 않습니다.)이 Method클래스는 설명하는 패턴을 매우 쉽게 구현하여 수천 명의 프로그래머의 삶을 더 쉽게 만들 수있었습니다.

물론,이 기술은 소스 레벨 제어를 가지고 있고 항상 가지고있는 밀접하게 결합 된 클래스의 작고 잘 정의 된 계층에서만 실행 가능합니다. 클래스 계층 구조가 그런 경우에는 그런 일을하고 싶지 않습니다. 자유롭지 않은 사람들이 기본 계급을 리팩토링 할 수 있도록 확장 할 수 있습니다.

다음은 내가 한 것과 귀하가 한 것과 다른 점입니다.

  • 기본 클래스는 전체 asDerivedClass()메소드 제품군에 대한 기본 구현을 제공하며 각 메소드는 각각 리턴 null합니다.

  • 파생 된 각 클래스는 asDerivedClass()메서드 중 하나만 재정의하고 this대신에를 반환 합니다 null. 나머지는 무시하지 않으며 그들에 대해 알고 싶지도 않습니다. 따라서 IllegalStateExceptions가 발생 하지 않습니다 .

  • 기본 클래스는 또한 다음과 같이 코딩 된 final전체 isDerivedClass()메소드 제품군에 대한 구현을 제공 합니다. return asDerivedClass() != null; 이 방법으로 파생 클래스에서 대체해야하는 메소드 수가 최소화됩니다.

  • 나는 @Deprecated생각하지 않았기 때문에이 메커니즘에서 사용 하지 않았습니다. 당신이 나에게 아이디어를 줬으니, 나는 그것을 사용하도록하겠습니다, 감사합니다!

C #에는 as키워드를 사용하여 관련 메커니즘이 내장되어 있습니다 . C #에서 당신이 말할 수있는 DerivedClass derivedInstance = baseInstance as DerivedClass당신은에 대한 참조를 얻을 것이다 DerivedClass당신이 경우 baseInstance클래스이었다, 또는 null이 아니었다면. 이것은 (이론적으로) is캐스트 보다 성능이 우수하지만 ( is명명 적으로 C # 키워드의 이름은 더 좋습니다 instanceof), 우리가 수공으로 만든 커스텀 메커니즘은 훨씬 더 나은 성능 instanceof을 제공합니다. asC # 의 연산자는 맞춤형 접근 방식의 단일 가상 메소드 호출만큼 빠르지 않습니다.

나는이 기술이 패턴 으로 선언되어야하며 좋은 이름을 찾아야한다고 제안했다.

이런, 공감 해줘서 고마워!

의견을 읽는 데 어려움을 겪지 않도록 논쟁의 요약 :

사람들의 반대 의견은 원래 디자인이 잘못되었다는 것입니다. 즉, 공통 기본 클래스에서 파생되는 클래스가 크게 다르거 나 그렇게하더라도 계층 구조를 사용하는 코드는 기본 참조이며 파생 클래스를 파악해야합니다. 그러므로 그들은이 질문과 원래의 디자인의 사용을 향상시키는 나의 대답에 의해 제안 된 자체 캐스팅 메커니즘은 처음에는 결코 필요하지 않았을 것이라고 말한다. (그들은 자체 캐스팅 메커니즘 자체에 대해 아무 말도하지 않으며 메커니즘이 적용되는 디자인의 특성에 대해서만 불평합니다.)

그러나, 나는 위의 예에서 한 이미 자바 런타임의 제작자가 사실은 대한 정확하게 같은 디자인 선택에서했던 것을 보여 java.lang.reflect.Member, Field, Method계층, 그리고 코멘트에 내가 아래 는 C # 런타임의 제작자가 독립적에 도착 보여 해당 에 대한 디자인 System.Reflection.MemberInfo, FieldInfo, MethodInfo계층 구조. 따라서 이들은 모두의 코 바로 아래에 있으며 정확하게 그러한 설계를 사용하여 입증 가능한 실행 가능한 솔루션을 가진 두 가지 실제 시나리오입니다.

그것은 다음의 모든 의견이 요약 한 것입니다. 자체 캐스팅 메커니즘은 거의 언급되지 않았습니다.


13
상자에 자신을 디자인했다면 합법적입니다. 그것은 이러한 많은 유형의 질문에 대한 문제입니다. 사람들은 디자인의 다른 영역에서 결정을 내리고, 실제 솔루션이 왜 이런 상황에 처하게되었는지 알아낼 때 이러한 유형의 해킹 솔루션을 강요합니다. 괜찮은 디자인은 여기에 당신을 얻을 수 없습니다. 현재 200K SLOC 응용 프로그램을보고 있는데이 작업을 수행해야하는 곳은 없습니다. 사람들이 책에서 읽는 것만이 아닙니다. 이러한 유형의 상황에 빠지지 않는 것은 단순히 모범 사례를 따르는 결과입니다.
Dunk

3
@Dunk 방금 예를 들어 java.lang.reflection.Member계층 구조 에 이러한 메커니즘이 필요하다는 것을 보여주었습니다 . 자바 런타임의 제작자는 이런 유형의 상황에 처해 있습니다. 자신이 스스로 상자에 디자인했다고 말하거나 모범 사례를 따르지 않았다고 비난하는 것은 아닙니다. 제가 여기서 만들고자하는 요점은 일단 이러한 유형의 상황이 발생하면이 메커니즘이 상황을 개선하는 좋은 방법이라는 것입니다.
Mike Nakis

6
@MikeNakis Java 제작자들은 많은 나쁜 결정을 내렸으므로 혼자서 말을 많이하지 않습니다. 어쨌든 방문자를 사용하는 것이 임시 테스트를 수행하는 것보다 낫습니다.
Doval

3
또한이 예에는 결함이 있습니다. 거기 인 Member인터페이스는하지만, 그것은 단지 액세스 한정자를 확인하는 역할을한다. 일반적으로 메서드 또는 속성 목록을 가져 와서 직접 사용하거나 혼합하지 않고 List<Member>표시하지 않으므로 캐스팅 할 필요가 없습니다. 참고 Class제공하지 않는 getMembers()방법. 물론 단서가없는 프로그래머는을 만들 수 List<Member>있지만, 그것을 만들고 List<Object>일부 Integer또는 추가 하는 것만 큼 의미 String가 없습니다.
SJuan76

5
@MikeNakis "많은 사람들이 그것을"및 "유명한 사람이 그것을"실제 논쟁이 아닙니다. 반 패턴은 나쁜 생각이자 정의에 의해 널리 사용되지만 이러한 변명은 그 사용을 정당화 할 것입니다. 어떤 디자인이나 다른 디자인을 사용하는 진짜 이유를 제시하십시오. 임시 캐스팅의 문제는 API를 사용하는 모든 사용자가 매번 캐스팅을 올바르게 수행해야한다는 것입니다. 방문자는 한 번만 정확하면됩니다. 일부 메소드 뒤에 캐스팅을 숨기고 null예외 대신 반환 하면 훨씬 좋아지지 않습니다. 다른 모든 평등은 동일한 작업을 수행하는 동안 방문자가 더 안전합니다.
Doval
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.