即使对象污染了其子类的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中使用。因此,我可以在上执行此操作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(); }

这样,如果已知对象仅为a Base,则不建议使用这些方法,并且可以使用这些方法将自身“转换”为其他类型,以便可以在其上调用子类的方法。在某种程度上,这对我来说似乎很优雅,但是,另一方面,我有点在滥用“不推荐使用的注释”,以从类中“删除”方法。

由于Sub1实例实际上是一个 Base,因此使用继承而不是封装确实有意义。我做得好吗?有解决这个问题的更好方法吗?


12
现在所有3个班级都必须彼此了解。添加Sub3将涉及很多代码更改,而添加Sub10将是非常痛苦的
事情

15
如果您给我们提供了真实的代码,将会很有帮助。在某些情况下,根据某类事物的特定类别来做出决策是适当的,但是无法判断您是否有理由对这种人为的示例进行处理。无论价值多少,您想要的是Visitor或带标签的union
2015年

1
抱歉,我认为简化示例变得容易。也许我会发布新的问题,涉及范围更广。
密码破解者2015年

12
您只是在重新实现强制转换,并且instanceof,它需要大量的输入,容易出错,并且很难添加更多的子类。
user253751

5
如果Sub1并且Sub2不能互换使用,那么为什么要这样对待它们呢?为什么不分别跟踪您的“三明治制造者”和“外来接触者”?
Pieter Witvoet

Answers:


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的lambda和流技术,您将可以

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:这种事情通常在对求和类型和模式匹配具有一流支持的语言中使用,即使在语法上非常丑陋,它也是Java中的有效设计。
Mankarse 2015年

@Mankarse一点点语法糖就可以使它远离丑陋-我已经举了一个例子。
Michael Anderson

假设假设OP改变了主意并决定添加一个Sub3。访客是否有与完全不一样的问题instanceof?现在,您需要添加onSub3到访客中,如果您忘记了,它将中断。
Radiodef

1
@Radiodef使用访问者模式而不是直接打开类型的一个优点是,一旦将其添加onSub3到访问者界面,则在创建尚未更新的新Visitor的每个位置都会出现编译错误。相反,打开类型充其量只会产生运行时错误-触发起来很棘手。
迈克尔·安德森

1
@FiveNine,如果您乐意将完整的示例添加到答案的结尾,可能会对其他人有所帮助。
迈克尔·安德森

83

在我看来:您的设计是错误的

翻译成自然语言,您说的是以下内容:

有了我们animals,有catsfishanimals有特性,这是共同的catsfish。但是,这还不够:有一些属性,分化catfish,因此,你需要的子类。

现在您有一个问题,您忘记为运动建模了。好的。这是相对容易的:

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

但这是一个错误的设计。正确的方法是:

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

move每个动物在哪里都将实施不同的共享行为。

由于这些方法采用不同的参数并且做的事情完全不同,因此它们是完全不兼容的,我不能仅使用多态来解决此问题。

这意味着:您的设计已损坏。

我的建议:重构BaseSub1Sub2


8
如果您提供实际的代码,我可以提出建议。
Thomas Junk

9
@codebreaker如果您认为自己的示例不好,我建议接受此答案,然后写一个新问题。不要修改问题以使用其他示例,否则现有答案将毫无意义。
Moby Disk

16
@codebreaker我认为这可以通过回答实际问题来“解决” 问题。真正的问题是:如何解决我的问题?正确重构代码。重构使您.move().attack()妥善抽象that的部分-而不是有.swim().fly().slide().hop().jump().squirm().phone_home(),等你如何重构正确?没有更好的示例,还是个未知数……但通常还是正确的答案-除非示例代码和更多详细信息另有说明。
WernerCD 2015年

11
@codebreaker如果您的类层次结构导致您遇到这种情况,那么按照定义,这是没有意义的
Patrick Collins

7
@codebreaker与Crisfole的最新评论相关,还有另一种查看它的方法可能会有所帮助。例如,对于movevs fly/ run/ etc,可以用一句话来解释为:您应该告诉对象该做什么,而不是该如何做。在访问者方面,正如您提到的,它更像此处评论中的真实案例,您应该问对象问题,而不是检查其状态。
Izkata 2015年

9

很难想象您遇到一堆东西并希望他们做一个三明治或与外星人联系的情况。在大多数情况下,您会发现这种类型的转换将使用一种类型的操作-例如,在clang中,您过滤了一组声明用于getAsFunction返回非null的声明的节点,而不是对列表中的每个节点执行不同的操作。

可能是您需要执行一系列操作,而执行该操作的对象之间实际上并不相关。

因此,而不是列表,而是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对象只会使情况变得更糟。我也可能只是将Objects 传递给方法,对其进行强制转换,并希望它们是正确的类型。
密码破解者

1
@codebreaker然后您确实需要澄清您的问题-在大多数系统中,有充分的理由调用一堆与它们的功能无关的函数;例如处理事件规则或执行模拟步骤。通常,此类系统具有发生动作的上下文。如果“ getRandomIngredients”和“ getFrequency”不相关,则不应位于同一对象中,您还需要另一种方法,例如使动作捕获成分或频率的来源。
Pete Kirkham

1
您是对的,我认为我无法解决这个问题。我最终选择了一个不好的例子,所以我可能会再发表一个例子。getFrequency()和getIngredients()只是占位符。我可能应该只将“ ...”作为参数,以使其更明显地表明我不知道它们是什么。
密码破解者2015年

这是IMO的正确答案。知道Context不需要是新类,甚至不需要接口。通常情况下,上下文只是调用者,因此调用形式为action.act(this)。如果getFrequency()getRandomIngredients()是静态方法,则可能甚至不需要上下文。这就是我要实施的工作队列,您的问题听起来很糟糕。
QuestionC

同样,这与访问者模式解决方案基本相同。不同之处在于,您正在将Visitor :: OnSub1()/ Visitor :: OnSub2()方法实现为Sub1 :: act()/ Sub2 :: act()
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循环将变为:

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

这种方法有两个主要优点:

  • 不涉及演员
  • 您可以让每个子类实现所需数量的接口,这确实为将来的更改提供了一定的灵活性。

PS:很抱歉,接口的名称,在那个特定时刻我想不出任何更酷的东西:D。


3
这实际上并不能解决问题。您仍然需要在for循环中进行强制转换。(如果您不这样做,则也不需要OP示例中的强制类型转换)。
塔伊米尔2015年

2

如果一个家庭中的几乎任何类型可以直接用作满足某些标准的某个接口的实现,或者可以用于创建该接口的实现,则该方法可能是一个好方法。恕我直言,内置的收集类型会从这种模式中受益,但是由于它们并非出于示例目的,因此我将创建一个收集接口BunchOfThings<T>

的某些实现BunchOfThings是可变的;有些不是。在许多情况下,Fred的对象可能想要保留它可以用作a的对象,BunchOfThings并且知道Fred不能修改它。可以通过两种方式满足此要求:

  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来自getRandomIngredientsmakeASandwich,但YMMV 的风险。


2
赶上ClassCastException只是ew。这就是instanceof的全部目的。
Radiodef 2015年

@Radiodef:是的,但目的之一 <是避免ArrayIndexOutOfBoundsException,有些人也决定这样做。不考虑味道;-)基本上,我的“问题”是我在Python中工作,首选样式通常是捕获任何异常都比事前测试是否会发生异常更可取,无论事前测试多么简单。也许这种可能性在Java中不值得一提。
史蒂夫·杰索普

@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”实际上并不意味着任何东西。它与我正在做的任何事情都不对应。另外,关于您的最后一句话:保证类型安全性如何?
密码破解者2015年

@codebreaker-它与示例中循环中的操作完全对应。如果那没有意义,请不要编写循环。如果它在方法上没有意义,那么在循环体中就没有意义。
2015年

1
与您的方法“保证”类型安全一样,强制转换也是如此:如果对象类型错误,则引发异常。
Random832'2015-4-29

我意识到我选了一个不好的例子。问题在于,子类中的大多数方法更像是吸气剂,而不是变异方法。这些类本身并不做任何事情,仅提供数据。多态性对我没有帮助。我要与生产者而不是消费者打交道。另外:抛出异常是运行时的保证,不如编译时好。
密码破解者2015年

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,但没有contactAliens。
码破解者2015年

-5

您所做的完全合法。不要因为那些在某些书中读到过教条而反对只是重复教条的反对者。教条在工程学中没有地位。

我已经使用过相同的机制几次,并且可以自信地说,Java运行时也可以在我可以想到的至少一个地方完成相同的操作,从而提高了代码的性能,可用性和可读性。使用它。

举个例子java.lang.reflect.Member,它是基体java.lang.reflect.Fieldjava.lang.reflect.Method。(实际的层次结构要复杂得多,但这无关紧要。)字段和方法是千差万别的动物:一个具有您可以获取或设置的值,而另一个则没有这样的东西,但是可以使用大量参数,并且可能返回值。因此,字段和方法都是成员,但是您可以使用它们做的事情与做三明治和联系外星人一样彼此不同。

现在,在编写使用反射的代码时,我们经常会Member手握一个a ,并且我们知道它要么是Methoda Field,要么是a (或者很少是其他东西),但是我们必须做所有繁琐的工作instanceof才能精确地找出它是什么,然后我们必须对其进行强制转换以获得正确的参考。(这不仅繁琐,而且效果也不佳。)Method该类可以非常轻松地实现您所描述的模式,从而使成千上万的程序员的生活变得更轻松。

当然,此技术仅在您拥有(并将始终拥有)源级控制的紧密耦合的类的小型,定义良好的层次结构中才可行:如果您的类层次结构是可能会被不自由重构基础阶级的人所扩大。

这是我所做的与您所做的不同的地方:

  • 基类为整个asDerivedClass()方法系列提供默认实现,使每个方法都返回null

  • 每个派生类仅覆盖asDerivedClass()方法之一,this而不是返回null。它不会覆盖其余的任何内容,也不希望了解有关它们的任何信息。因此,不会IllegalStateException抛出。

  • 基类还提供final了整个isDerivedClass()方法家族的实现,其编码如下:return asDerivedClass() != null; 这样,派生类需要重写的方法的数量将最小化。

  • 我没有@Deprecated在这种机制中使用过,因为我没有想到它。既然您给了我这个主意,我将使用它,谢谢!

C#具有通过使用as关键字内置的相关机制。在C#中,您可以说,DerivedClass derivedInstance = baseInstance as DerivedClass并且您将获得对a的引用(DerivedClass如果您baseInstance属于该类,或者null不是)。从理论上讲,此方法的性能要优于is强制转换((is是公认的更好的C#关键字instanceof,),),但我们手工制作的自定义机制的性能甚至更好:instanceofJava 的一对铸造操作因为asC#的运算符的执行速度不如我们的自定义方法的单个虚拟方法调用快。

我在此提出一个命题,即应将此技术声明为一种模式,并为其找到一个好的名称。

哎呀,谢谢你的投票!

争议摘要,可帮助您免去阅读评论的麻烦:

人们的反对意见似乎是,原始设计是错误的,这意味着您永远都不应拥有从一个通用基类派生的截然不同的类,或者即使您这样做,使用这种层次结构的代码也永远不应处于基本参考,需要弄清楚派生的类。因此,他们说,这个问题和我的回答所提出的自我投射机制可以改善原始设计的使用,一开始就没有必要。(他们并没有真正谈论自广播机制本身,他们只是抱怨该机制打算应用于的设计的性质。)

然而,在上面我的例子都已经表明,Java运行时的创造者的确在选择的正是这样的设计java.lang.reflect.MemberFieldMethod层次结构,并在评论下面我表明,C#运行时的创作者在独立抵达相当于设计为System.Reflection.MemberInfoFieldInfoMethodInfo层次结构。因此,这是两种不同的现实世界场景,它们正好坐在每个人的鼻子下面,并且使用精确的此类设计具有可证明可行的解决方案。

这就是以下所有评论的内容。几乎没有提到自铸造机制。


13
如果您将自己设计成盒子,那是合法的。这就是很多这类问题的问题。当真正的解决方案首先要弄清楚为什么您最终遇到这种情况时,人们会在设计的其他领域做出决定,迫使这些类型的骇人听闻的解决方案。体面的设计不会吸引您。我目前正在查看一个200K SLOC应用程序,但没有看到我们需要执行此操作的任何地方。因此,这不仅仅是人们在书中阅读的内容。避免陷入此类情况仅仅是遵循良好做法的最终结果。
Dunk 2015年

3
@Dunk我只是表明,例如在java.lang.reflection.Member层次结构中存在对这种机制的需求。Java运行时的创建者遇到了这种情况,我不认为您会说他们将自己设计成一个盒子,或者怪他们没有遵循某种最佳实践吗?因此,我在这里要指出的是,一旦遇到这种情况,这种机制就是改善情况的好方法。
Mike Nakis 2015年

6
@MikeNakis Java的创建者做出了很多错误的决定,因此仅此而已。无论如何,使用访问者比进行临时测试然后广播更好。
2015年

3
此外,该示例是有缺陷的。有一个Member接口,但仅用于检查访问限定符。通常,您会获得方法或属性的列表并直接使用(不混合它们,因此不会List<Member>出现),因此无需强制转换。注意,Class没有提供getMembers()方法。当然,一个笨拙的程序员可能会创建List<Member>,但是这与将其List<Object>添加到其中Integer或添加其中一样没有多大意义String
SJuan76

5
@MikeNakis“很多人都这样做”和“著名人都这样做”不是真正的论点。反模式既是坏主意,又被定义广泛使用,但这些借口将证明其合理性。给出使用某种设计的真实原因。临时强制转换的问题在于,您的API的每个用户每次都必须正确进行强制转换。访客只需要正确一次即可。将转换隐藏在某些方法后面并返回null而不是异常不会使它变得更好。在所有其他条件相同的情况下,访客在执行相同工作时会更安全。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.