0

假设我有一个 A 类

class A
{
   Z source;
}

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

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

但是有些东西仍然不能说服我,因为每次使用 A 类的实例时,我都需要知道“源”的类型。所以所有都在多个“ifs”中完成,使得“is instanceof”听起来不太好。也许将来其他一些类实现 Z,并且硬编码这种类型的“ifs”肯定会破坏某些东西。

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

我希望有人能给我和建议,也许是一些有用的设计模式。

谢谢

编辑:当获得某个 A 的实例时,“某人”在某个地方所做的工作完全不同,具体取决于接口 Z 后面的类。这就是问题所在,做“重要工作”的实体不是 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
4

3 回答 3

6

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

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

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

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

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

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

链接

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

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

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

于 2012-10-17T22:50:41.017 回答
1

我认为您需要发布更多信息,因为就目前而言,我所看到的是对 OOP 原则的误解。如果您使用通用接口类型,则根据 Liskov 替换原则,源是哪种类型无关紧要。

于 2012-10-17T22:51:15.683 回答
1

我将把你的 A、B 和 C 类称为 Alpha、Beta 和 Gamma。

也许 Alpha 可以分为两个版本,一个使用 Beta,一个使用 Gamma。这将避免instanceofAlpha 中的检查,正如您所推测的那样,这确实是一种代码味道。

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(); }
}
于 2012-10-17T22:52:14.690 回答