【问题标题】:Is it good practice to often use instanceof?经常使用instanceof是一种好习惯吗?
【发布时间】:2015-09-02 19:56:58
【问题描述】:

场景。我正在编写与游戏相关的代码。在那个游戏中,Player(它也是一个类)有一个Item 的列表。还有其他类型的项目继承自Item,例如ContainerItem、DurableItem 或WeaponItem。

显然,只有List<Item> 对我来说非常方便。但是当我得到玩家的物品时,我区分物品类型的唯一方法是使用instanceof关键字。我确定我已经读过依赖它是不好的做法。

在这种情况下可以使用它吗?还是我应该重新考虑我的所有结构?

【问题讨论】:

  • 如果Item定义了一个抽象方法,就可以避免使用instanceof,强制每个子类实现自己的。
  • 可能会重新考虑您的结构。您描述的模式实际上是一种反模式。
  • 是的,通常使用 `instanceof` 是糟糕/糟糕设计的表现。诀窍是使用双重调度或访问者模式。上网看看。
  • 如果您提供一些有关您如何使用instanceof 的详细信息,给您一些示例会更容易。

标签: java hierarchy instanceof


【解决方案1】:

假设我正在编写一些库存代码:

public void showInventory(List<Item> items) {
    for (Item item : items) {
        if (item instanceof ContainerItem) {
            // container display logic here
        }
        else if (item instanceof WeaponItem) {
            // weapon display logic here
        }
        // etc etc
    }
}

这将编译并正常工作。但是它错过了面向对象设计的一个关键思想:您可以定义父类来做一般有用的事情,并让子类填充特定的重要细节。

上述的替代方法:

abstract class Item {
    // insert methods that act exactly the same for all items here

    // now define one that subclasses must fill in themselves
    public abstract void show()
}
class ContainerItem extends Item {
    @Override public void show() {
        // container display logic here instead
    }
}
class WeaponItem extends Item {
    @Override public void show() {
        // weapon display logic here instead
    }
}

现在我们可以在所有子类中查看库存显示逻辑的show() 方法。我们如何访问它?简单!

public void showInventory(List<Item> items) {
    for (Item item : items) {
        item.show();
    }
}

我们将所有特定于项目的逻辑保留在特定的项目子类中。这使您的代码库更易于维护和扩展。它减少了第一个代码示例中长 for-each 循环的认知压力。并且它准备好 show() 可以在您尚未设计的地方重复使用。

【讨论】:

  • 这是正确的做法,但有时还不够。以Record 类为例——我们有两种记录——File 和Directory。常见的东西可以在记录中抽象出来,但假设File 有一个download() 方法。所以从Set&lt;Record&gt; 你不能下载这些文件,除非你知道它们确实是文件。因此,如果您陷入这种情况,多态性是不够的,但在大多数情况下,它是最好的解决方案,所以 +1 表示好的答案
  • @SvetlinZarev 您也可以将其抽象化——通过实现有意义的方法来抛出 UnsupportedOperationException(例如 java.util.Iterator.remove),提供 Null-Object/Default 返回或添加方法查询方法是否适用。这都是努力的问题,而不是主要的不可能。
  • 我认为向接口添加方法是糟糕的设计,只是为了抛出UnsupportedOperationException。空对象模式的目的也完全不同。是的,它可能会完成工作,但它更像是一个黑客,而不是真正的解决方案。以我的“记录”示例为例。为什么要使用不适用于每条记录的方法污染Record 接口?通过这样做,我只会让 API 更难使用 - 即客户端将不得不处理愚蠢的异常或无用的返回值。
  • @svetlin zarev 如何在您的示例中实现下载方法?
【解决方案2】:

恕我直言,使用instanceof 是一种代码味道。简而言之 - 它使您的代码程序化,而不是面向对象。执行此操作的 OO 方法是使用 visitor pattern。

访问者模式还允许您在其之上轻松构建decorators 和chain of responsibility,从而实现关注点分离,从而使代码更短、更简洁、更易于阅读和测试。

您还真的需要知道确切的班级吗?你不能利用多态性吗?毕竟Axe 和Sword 一样是Weapon。

【讨论】:

  • 我不同意,viditor 模式允许您在其上轻松构建decorator 和chain of responsibility 以实现separation of concerns,从而使您的代码更简单、更易于阅读。它还强制执行类型安全 - 您无法添加新的 T 子类而不会遇到编译时错误,而 instanceoff 您将在运行时发现问题
  • 我的评论并不严肃,但我反复尝试了解访问者模式,但从未成功。我不得不使用访问者模式维护代码,发现它令人沮丧。
  • 好吧,我需要做一些激烈的谷歌搜索才能理解这种模式......但如果我错了,请纠正我,但这与@Matt Spephensons 的帖子不是很相似吗?
  • 不,不是。马特也在我之后发布了它。他正在向您展示如何利用语言的多态特性。基本上他的方法是解决问题的最佳方法,但有时这还不够 - 请参阅我在他的帖子下的评论。我的帖子是关于instanceof 的OO 方式——这是一种称为double dispatch 的技术,它是通过访问者模式实现的。但不要误会我的意思。我的回答与他的不同或相反。它是免费的。如果您没有正确实现类层次结构,则无法实现访问者。
  • 我没有冒犯的意思。访问者模式对我来说似乎很复杂,我看到的教程与他的代码非常相似。
【解决方案3】:

也许你应该重新考虑并尝试使用多态性来实现你的List&lt;Item&gt; 想法。

这里有一些可能对您的问题有所帮助的参考资料:


(来自Is instanceof considered bad practice? If so, under what circumstances is instanceof still preferable?的引用)

【讨论】:

    【解决方案4】:

    你应该重新考虑你的结构,非元代码中的instanceof 几乎总是反模式的标志。尝试在Item-class/interface 中定义所有Items 的共同行为(比如有图片、描述以及单击它们时发生的事情),使用abstract-keyword if适当的,然后使用polymorphism来实现细节。

    【讨论】:

      【解决方案5】:

      如果你容易理解就好了。

      将分支逻辑从它们自然属于的所有位置移至子类不一定是一个好主意。它可能会创建错误的依赖关系;它可能会导致类臃肿,并且可能难以导航和理解。

      归根结底,这一切都是关于如何在一维空间中以多个维度的关注点物理组织我们的代码。这不是一个小问题,它是主观的,没有灵丹妙药。

      特别是,我们的行业继承了上个世纪基于那个时代的技术和经济限制而制定的许多经验法则。例如,工具非常有限;程序员非常昂贵;应用程序发展缓慢。

      其中一些规则今天可能不再适用。

      【讨论】:

        【解决方案6】:

        我不一定认为 instanceof 对于知道自己在做什么并使用它来避免编写更复杂的代码来绕过它的编码人员来说是坏事。万物皆有用,也有误用。

        话虽如此,您提供的描述不需要instanceof。有多种方法可以在没有 instanceof 的情况下实现这一点,并且(最重要的是)替代解决方案必须比使用 instanceof 更好。不要仅仅使用非实例化解决方案来避免实例化,因为你听说它很糟糕。

        我认为对于您的方案,非实例解决方案有利于使解决方案更易于扩展。

        for (Item i: items) {
            if (ItemTypes.WEAPON_ITEM.equals(i.getType)) {
                processWeaponType(i);
            }
        
        }
        

        或

        for (Item i: items) {
            if (WeaponItem.class.equals(i.getType)) {
                processWeaponType(i);
            }
        
        }
        

        【讨论】:

        • 好吧,但您的解决方案并不比instanceof 更具可读性。性能方面,instanceof 非常快;这绝对比比较我们自己的type 字段要快;它甚至可能比比较 Class 和 == 更快。
        • 也许,但就这个问题而言,速度是期望的结果还是软件设计和架构?我的意思是,如果您真的认为性能差异值得考虑这个问题,我们可以一起摆脱类,只需将所有内容放在一个长方法中。由于 JIT 编译器无论如何都会重新组织您的代码,因此很难判断哪个更快。
        • 这并不比instanceof好,它是instanceof的伪装。这两个建议都没有解决添加新子类型时必须检查/更新所有客户端代码的问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-03-06
        • 2013-03-30
        • 1970-01-01
        • 2015-02-18
        • 2016-03-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多