'is instanceof'界面糟糕的设计

时间:2012-10-17 22:43:30

标签: oop design-patterns

说我有一个A级

class A
{
   Z source;
}

现在,上下文告诉我'Z'可以是不同类的实例(比如B和C),它们不会在它们的继承树中共享任何公共类。

我想天真的方法是让'Z'成为一个接口类,让类B和C实现它。

但有些事情仍然无法说服我,因为每次使用A类实例时,我都需要知道'source'的类型。因此,所有完成的多个'ifs'制作'是instanceof'听起来不太好。也许在将来其他一些类实现Z,并且拥有这种类型的硬编码'ifs'肯定会破坏某些东西。

问题的存在是我无法通过向Z添加函数来解决问题,因为在Z的每个实例类型中完成的工作是不同的。

我希望有人可以给我提供建议,也许可以提供一些有用的设计模式。

由于

编辑:根据界面Z背后的类,获得A的一些实例时,“某人”在某个地方所做的工作完全不同。这就是问题,执行“重要工作”的实体不是Z,是其他想知道谁是Z的人。

Edit2:也许一个具体的例子会有所帮助:

class Picture
{
  Artist a;
}

interface Artist
{
}

class Human : Artist { }

class Robot : Artist {}

现在某个地方我有一个Picture的实例,

Picture p = getPicture();
// Now is the moment that depending if the type of `p.a` different jobs are done
// it doesn't matter any data or logic inside Human or Robot

3 个答案:

答案 0 :(得分:6)

使用接口的目的是隐藏这些不同的实现; A应该只知道方法的意图或高级目的。

Z的每个实现所完成的工作可能不同,但用于调用该工作的方法签名可以是相同的。类A只能调用方法Z.foo(),并且根据Z的实现是B还是C,将执行不同的代码。

您需要知道实际实现类型的唯一时间是您需要在两种不同类型上执行完全不相关的处理,并且它们不共享接口。但在那种情况下,为什么他们被同一个A级处理?现在,这种情况可能有意义,例如当BC是从XML Schema生成的类时,你无法修改它们 - 但通常它表明设计可以改进。

已更新,现在您已添加了图片示例。我认为这证实了我的观点 - 虽然getPicture()的实现不同,但目的返回类型是相同的。在这两种情况下,艺术家都会返回一张图片。

如果调用者想要以相同的方式处理Robot创建的和人工创建的图片,那么他们使用Artist界面。他们不需要区分人类或机器人,因为他们只想要一张照片!创建图片的细节属于子类,调用者不应该看到这些细节。如果调用者精确地如何创建图片,那么调用者应该绘制它,而不是机器人或人类,并且设计会有很大不同。

如果您的子类正在执行完全无关的任务(这不是您的Artist示例所显示的内容!)那么您可能会使用非常模糊的界面,例如标准Java Runnable;在这种情况下,调用者真的不知道run()方法会做什么 - 它只知道如何运行Runnable的东西。

<强>链接

以下问题/文章提出了instanceof的一些替代方案:

以下文章还提供了示例代码,使用了与您类似的示例:

以下文章讨论instanceof与其他方法(如访客模式和非循环访问者)之间的权衡:

答案 1 :(得分:1)

我认为您需要发布更多信息,因为我认为这是对OOP原则的误解。如果您使用了通用接口类型,那么通过Liskov替换原则,无论哪种类型的源都是无关紧要的。

答案 2 :(得分:1)

我要把你的A,B和C类称为Alpha,Beta和Gamma。

也许Alpha可以分为两个版本,一个使用Betas,另一个使用Gammas。这样可以避免在Alpha中进行instanceof检查,正如您所推测的那样,确实是代码味道。

abstract class Alpha
{
    abstract void useSource();
}

class BetaAlpha extends Alpha
{
    Beta source;
    void useSource() { source.doSomeBetaThing(); }
}

class GammaAlpha extends Alpha
{
    Gamma source;
    void useSource() { source.doSomeGammaThing(); }
}

事实上这非常普遍。考虑一个可以使用文件或套接字的Stream类的更具体示例。并且出于示例的目的,File和Socket不是从任何公共基类派生的。事实上,它们可能甚至不在我们的控制之下,所以我们无法改变它们。

abstract class Stream
{
    abstract void open();
    abstract void close();
}

class FileStream extends Stream
{
    File file;
    void open()  { file.open();  }
    void close() { file.close(); }
}

class SocketStream extends Stream
{
    Socket socket;
    void open()  { socket.connect();    }
    void close() { socket.disconnect(); }
}