8

如您所知,“程序到接口”设计原则广泛偏爱超类型而不是具体类型或实现。

在Java程序中使用instanceof从超类型派生具体类型是否符合原理?

在我的应用程序中,Storehouse 是一个抽象超类型类,具有几个私有变量和公共 getter 和 setter。

ConcreteStorehouseA 继承自 Storehouse,有很多具体的方法和变量。ConcreteStorehouseB 类似但不同。

我的应用程序收到一个仓库。但是,Storehouse 不是一个有用的类型。因为唯一真正有用的方法包含在具体类型中,所以我使用 instanceof 如下:

if (storehouse instanceof ConcreteStorehouseA) {
    ConcreteStorehouseA concreteStorehouseA = (ConcreteStorehouseA) storehouse;
    // perform operations on the concrete type's useful methods and variables

使用instanceof是否符合原理?

编辑:

本质上,该应用程序是桌面角色扮演游戏 Shadowrun 的骰子模拟器。具体类型是不同的测试类型——成功测试、反对测试、扩展测试——它们的成功运行都有非常不同的因素和参数。超类型本质上包含骰子池!

4

7 回答 7

10

根据经验,您提到的“程序到接口”原则可以翻译成:import 只有接口类型,对子类没有任何编译时依赖。

因此,您的问题的答案肯定是否定的。由于您转换为具体类型,因此您没有对接口进行编程。

于 2011-02-11T21:26:51.980 回答
8

你自己说过:

我的应用程序收到一个仓库。但是,Storehouse 不是一个有用的类型。因为唯一真正有用的方法包含在具体类型中

换句话说,你的Storehouse抽象并没有给你带来任何东西……你为什么拥有它?

您能否Storehouse在每个具体类中创建抽象方法,然后让您在客户端代码中以相同的方式处理具体类型?这就是抽象的目标。

于 2011-02-11T21:23:20.307 回答
2

并不真地。如果你的方法根据它接收的类型有非常不同的行为,那么多态性对你没有任何好处。您应该考虑两个单独的重载方法,一个采用 aConcreteStorehouseA作为参数,另一个采用 a ConcreteStorehouseB

于 2011-02-11T21:25:01.823 回答
2

这并不总是一种罪过。假设您正在实施一个规范,其中说,如果 x 是 A,则执行此操作,否则,如果 x 是 Y,则执行此操作。最好让您的代码看起来像规范。人为地把它切成小块,放在不同的源中,不仅做作,而且复杂、不安全、难以理解、难以维护。

编程关注点是多维的。编程语言是一维的,至少现在是这样。如何紧密地组织相关的关注点是一门艺术。不要相信关注点必须按类分解的教条,那么关注点最多只能引用一个类,并且它必须驻留在该类中。

据知道如何编写更好的软件的专家说,上个世纪软件工程中有一个很大的禁忌:即应不惜一切代价避免代码更改。如果您要更改之前编写的某些源代码,那么宇宙随时可能坍塌成花生。因此,您最好从一开始就设计一个完美的架构,然后可以通过添加一个新的/干净的类来完成任何需求更改,而无需触及现有的代码库。

也就是说,让我在这里措辞非常准确,完全是胡说八道,即使在当时也是如此。

今天,我们拥有更好的工具,代码更改不仅安全,甚至受到鼓励。由于不可预见的原因,确保明天代码更改很容易的最佳方法是让今天的代码尽可能简单。以一种即使是逗号也能理解的方式编写代码。

于 2011-02-11T22:35:26.533 回答
1

如果不深入了解您正在执行的操作,可能很难确定,但更优雅的设计是让 StoreHouse 为不同的“操作”定义方法签名。然后,您只需执行操作,而不是 if(instanceof) 检查,concreteStorehouses 将执行这些操作。

public abstract class Storehouse
//add these definitions;
public void operation1();
public void operation2();

public class ConcreteStorehouseA

public void operation1() {
   //do operation
}

public void operation2() {
   //do operation
}

public class ConcreteStorehouseB

public void operation1() {
   //do operation
}

public void operation2() {
   //do operation
}

在您的调用代码中,而不是:

if (storehouse instanceof ConcreteStorehouseA) {
    ConcreteStorehouseA concreteStorehouseA = (ConcreteStorehouseA) storehouse;
    // perform operations
} else if (storehouse instanceof ConcreteStorehouseB) {
    ConcreteStorehouseB concreteStorehouseB = (ConcreteStorehouseB) storehouse;
    // perform operations
}

然后,您可以使用多态性来执行操作,而无需关心正在执行哪个实现。

storehouse.operation1();
storehouse.operation2();

其中concreteStorehouseA 和ConcreteStorehouseB 已经为这些操作定义了实现。

于 2011-02-11T21:29:06.740 回答
1

在这种情况下,听起来Storehouse抽象根本不是真正的抽象。作为一种类型,它并没有真正为您提供任何东西。

可能有帮助的一件事是尽量不要将您的实现视为“类型”。“类型”是类正在实现的抽象。(基本上,在你的想法中将术语“类”和“类型”分开。)所以你正在使用的类型是Storehouse. ConcreteStorehouseAStorehouse, 也是ConcreteStorehouseB。那么问题就变成了,什么是a Storehouse?那是定义每个人是什么的类型。

在这种特殊情况下,听起来不同的Storehouse实现是如此不同(至少在它们当前的实现中),以至于它们并没有真正共享一个有价值的抽象。在这种情况下,抽象“类型”只是通过继承提供一些通用功能,而不是实际抽象类型。

换句话说,Storehouse这里的实现听起来像是 Liskov Substitution 激励海报的经典示例: http ://www.lostechies.com/blogs/derickbailey/archive/2009/02/11/solid-development-励志图片的原则.aspx

于 2011-02-11T21:31:07.423 回答
0

正如许多人所指出的,这不是对接口进行编程。显然,接口抽象对于您的要求来说太弱了。

有多种方法可以解决这个问题,其中:

  • 如果界面在您的控制之下,您可以展开它。考虑一下这是否值得,付出的代价可能太高了,尤其是在下一个要点成立的情况下。
  • 如果你最终只使用一种混凝土仓库,你可以直接使用它。
  • 如果您需要支持各种类型的仓库,一个不错的选择是定义您自己的接口,该接口准确地提供您需要的抽象,并为每种具体类(适配器模式)编写一个瘦包装器。
于 2011-02-11T21:31:40.463 回答