在抽象 class 或转换中实现 subclass 方法?

Implementing subclass methods in abstract class or casting?

假设我有一个 superclass Zone,它的目的是泛化更具体的 Zone 类型,它们在各自的 subclasses 中定义. 我想创建一个游戏板,它由一个 4x4 Zone 数组组成。玩家应该根据他们所在的 Zone 类型与区域进行不同的交互。

考虑以下超级class:

public abstract class Zone{        
        Pawn pawn;
        private boolean movable;

        public abstract boolean isMovable();
}

此 class 由另外两个 class 扩展,MountainZone:

public class MountainZone extends Zone{
        private int temperature;
        movable = true;

        public void FreezePawn(){
            super.pawn.freeze(); //suppose freeze() is implemented in Pawn class
        }

        public boolean isMovable(){
            return this.movable;
        }

        public int getTemperature(){
            return this.temperature;
        }
}

LavaZone:

public class LavaZone extends Zone{
        private int sulfurLevels;
        private int lavaFlowSpeed;
        movable = false;
        
        public boolean isMovable(){
            return this.movable;
        }

        public int getSulfurLevels(){
            return this.sulfurLevels;
        }

        public void setLavaFlowSpeed(int speed){
            this.lavaFlowSpeed = speed;
        }
}

最后,考虑以下 Main class 创建 gameboard 的地方:

public final class Main{
    private static Zone[][] gameboard = new Zone[4][4];

    public static void main (String[] args){

        for (Zone z : gameboard){
            if (z instanceof MountainZone){
                System.out.println(((MountainZone) z).getTemperature());
            } else if (z instanceof LavaZone){
                System.out.println(((LavaZone) z).getSulfurLevels());
            }
        }
    }
}

而不是在 superclass 中使用 null(或 0 对于 int)编写每个方法作为 getter 的 return 值并在它们各自的 subclasses,我倾向于使用如上所示的转换。这也适用于 Zone 的子 classes 中的任何其他方法。

我想知道,对于特定情况,与 gameboard 阵列上的各个区域进行交互的最佳方式是使用转换,还是从 subclass超级class.

简短的回答:反转控制和命令模式的组合。

如果你给 all 区域类型 'getSulphurLevels' 方法,那可能很奇怪;它似乎不适用于非熔岩区。当然,它可以工作,但是你需要改变你的应用程序工作方式的心智模型:如果所有区域都有硫含量,但大多数区域都为 0,那么这工作正常,你可以添加:

public int getSulphurLevels() {
    return 0;
}

class Zone,并仅在 LavaZone 中覆盖它。尽管如此,该策略并不一定适用于您可能想对特定区域类型执行的所有奇怪操作。因此:

控制反转

让我们看看 for (Zone z : gameboard) 循环。它试图做什么?它试图打印一些对该特定区域特别重要的非常基本的信息。所以,实现 THAT:

class Zone {
    public String renderImportantInformation() {
        return "Nothing special going on here";
    }
}

class LavaZone {
    public String renderImportantInformation() {
       return String.format("Sulphur is at %d density", getSulphurLevel());
    }
}

这通常有用,但无论如何,如果您想要非通用逻辑,那么您需要决定:要么区域携带此信息,要么董事会携带。通常区域是正确的地方,但每隔一段时间你就会在上面得到第三层: LavaZone 本身并没有真正意义的代码,但更多的是组合 属性:On特定的板类型,例如,对于特定区域,适用特殊规则。假设您有一个 'modification' 引擎,可以在其中使用备用规则调整游戏,并且您有一个 class 代表备用规则集。那将如何运作?

然后是命令模式:创建一个地图,将相关信息(对于一个简单的模式,可能只是区域类型)映射到知道如何处理它的代码块上:

private static final Map<Class<? extends Zone>, Function<Zone, String>> pertinentPropertiesPrinters = new HashMap<>();

static {
pertinentPropertiesPrinters.put(LavaZone.class, zone -> 
  String.format("Sulphur levels: %d", ((LavaZone) zone).getSulphurLevel());
}

稍微不幸的是,泛型不够强大,无法让您在这里避免强制转换,但在实践中,您可以通过为给定区域添加 'handler' 的单一方法来掩盖这一点负责铸造一次。

命令模式有些高级 - 在使用这些模式之前需要阅读更多内容:我想我会给你一个关于它们是什么的大致概念,但我会首先尝试制作能够准确描述更一般的方法您正在尝试做的事情(例如:打印相关信息、检查危险、询问玩家是否要执行属于该区域的特殊操作等)。