如何在多个不同的对象上调用相同的 tick-method?

How to call same tick-method on multiple different objects?

尝试制作 JavaFX 游戏,但无法全神贯注地思考如何在不同对象上使用 tick 方法,同时能够调用接口方法之外的其他方法。为了您的乐趣制作了这个简化的代码:

interface TickInterface {
    public void tick(); // i.e to move the object or to check for collision.
}

class Car implements TickInterface {
    void tick(){
        // run on every tick
    }

    void refuel(){
        /* 
        could be also any other method which is not run
        in every tick, like unlocking the car or getLocation()
        */
    }
}

class Bicycle implements TickInterface {
    void tick(){
        // run on every tick
    }
}

class LoopClass(){
    ...
    tickInterface car = new Car();
    tickInterface bicycle = new Bicycle();

    LoopClass(){
        ArrayList<TickInterface> rides = new ArrayList<TickInterface>();
        rides.add(car);
        rides.add(bicycle);

    void thisLoopsEveryFrame(){
        for(TickInterface ride : rides){
            ride.tick();
        }
    }

    void refuelCar(){
        car.refuel(); //not possible because of the interface object type
    }
}

我想在具有相同接口的两个不同对象上调用 tick(),但这导致我无法从 Car 对象调用 refuelCar()。当然,你不应该给自行车加油。执行更新循环(滴答)功能的标准方法是什么?苦恼找不到解决方法。

这是糟糕的方法,但您可以:

 ((Car) car).refuel();

我认为您必须创建名称类似于 Refuelvoid refuel(); 方法的接口。

你必须决定:

继承->interface Refuel extends TickInterfaceclass Car implements Refuel

实现->class Car implements Refuel,TickInterface

这取决于您的任务和应用程序架构。

您在需要逻辑的 class 中执行逻辑。

class Car implements TickInterface {
    void tick(){
        if (lowOnFuel) {
          refuel();
        {

    }
    void refuel(){ 

    }
}

-编辑-

我显然不知道你到底在做什么,但引入一个玩家会改变一切。

我会 update/tick 你的玩家 class 并让他知道他在驾驶什么,因为这是有道理的。因此,如果他正在驾驶 Car 通过 Car playerCar = new Car() 实例化它,或者如果您真的想编程到一个接口(在大多数情况下这是很好的做法),您可以这样做。

  interface Vehicle {
    void accelerate();
    void steerLeft();
    //...
  }

  If (forwardIsPressed) {
    vehicle.accelerate();
  }
  if (leftIsPressed) {
    myCar.steerLeft();
  }   

  if (playerWantsToRefuel) {
    if (vehicle instanceof Car) {
        // safe to cast into a car object. 
        Car myCar = (Car) vehicle;
        myCar.refuel;
    } else if (vehicle instanceof Bike)
    {
        UI.ShowDialogueBox("You cannot refuel a bike, go eat a something to refuel your energy.");
    }
  }

如您所见,我去掉了 TickInterface,因为它不再有意义了。玩家和 AI 正在驾驶汽车,因此也许可以让它们具有带有勾选或更新功能的界面 'Driver'。然后让他们控制他们驾驶的车辆。在玩家的情况下,如果按下某个键,您将在从游戏循环调用的 update/tick 方法中调用他驾驶的汽车的该功能。我希望这是有道理的。

您仍然可以使用类似 Drive() 的车辆界面,您可以在其中降低汽车的燃油。你的自行车的燃料问题仍然存在,玩家再次需要知道他骑的是什么才能使用它的功能。以侠盗猎车手为例,所有车辆都可以有相同的界面,只是行为发生了变化。但是,如果 GTA 汽车需要加油而自行车不需要,那么自行车将与汽车有很大不同。仍然两者都可以从界面继承加油方法,但是自行车会显示一条消息,表明它无法加油,如果这对你有用,那就太好了,但如果它没有意义,那很可能是糟糕的设计。

我还建议您阅读更多关于接口的内容,以便更好地理解它们。 Here is a great answer already.

如前所述,如果您只想保留当前的体系结构,您始终可以将对象显式类型转换为其子class,并以这种方式轻松访问其所有专门的 public 方法.您也可以使用instanceof运算符先检查对象的实例类型,以确保您可以应用该方法。但是,我也认为在您的上下文中使用它是不好的做法。继承/接口通常用于抽象子classes共享的公共属性,独立于它们的具体实现。所以分享 "tick"-属性 是有意义的,但是你的 LoopClass 不应该关心每个 tick 中发生的事情。

我建议您在单独的 classes 中外包控制逻辑(或者如果需要,将其包含在您的游戏对象实例 classes 中)并使用对象的显式子类型。如果您的控件 class 需要修改两辆车,那么它对这两辆车都有了解是有道理的。所以例如您的 Car class 可以使用对 Bicycle 对象的引用(可能由自己的属性提供)并在发生冲突时在其 tick() 方法中操作两个对象。

TL;DR:你可以做到

class LoopClass(){
    ...
    Car car = new Car();
    Bicycle bicycle = new Bicycle();

    LoopClass(){
        ArrayList<TickInterface> rides = new ArrayList<TickInterface>();
        rides.add(car);
        rides.add(bicycle);
    }

    void thisLoopsEveryFrame(){
        for(TickInterface ride : rides){
            ride.tick();
        }
    }

    void refuelCar(){
        car.refuel(); // possible now car has compile-time type of Car
    }
}

解释:

你混淆了 "compile-time type" 和 "runtime type":声明

I want to call tick() on both different objects with same interface but this causes me not being able to call refuelCar() from Car object.

不正确。

一个对象实际的方法,即对象的成员,是由运行时对象在内存中的实际类型决定的("runtime type" ).这又由用于创建对象的构造函数决定。

所以当你写

TickInterface car = new Car();

然后当这段代码在运行时执行时,它会在内存中(在堆上)创建一个 Car 类型的对象。您可以将此对象视为同时具有 tick() 方法和 refuel() 方法。

另一方面,编译器允许您调用的方法由编译时类型决定:即的类型引用变量 用于引用对象。

通过写作

TickInterface car ;

您创建了编译时类型 TickInterface 的引用(称为 car)。这意味着编译器只会让你调用

car.tick();

(因为编译器知道 carTickInterface 类型,并且它知道 TickInterface 声明了一个名为 tick() 的方法),但它不会让你这样做

car.refuel();

因为并非每个 TickInterface 实例都有一个名为 refuel() 的方法。

当您使用

car 赋值时
car = new Car();

您正在执行 upcast= 右边的表达式类型是 Car,而左边的表达式类型是 TickInterface。因为编译器确信每个 Car 实例也是一个 TickInterface 实例,所以这是完全合法的。

当您将 car 添加到您的列表时:

rides.add(car);

您有效地创建了对您创建的 Car 对象的第二个引用。第二个引用内部保存在 List 中。由于您将列表声明为 TickInterface 类型,因此

List<TickInterface> rides = new ArrayList<TickInterface>();

您也可以将隐藏的内部引用视为编译时类型 TickInterface

但是,这两个引用没有理由是同一类型。你可以做到

Car car = new Car();
Bicycle bicycle = new Bicycle();

LoopClass(){
    ArrayList<TickInterface> rides = new ArrayList<TickInterface>();
    rides.add(car);
    rides.add(bicycle);

void thisLoopsEveryFrame(){
    for(TickInterface ride : rides){
        ride.tick();
    }
}

现在 car 具有 编译时 类型 Car(并且 bicycle 具有编译时类型 Bicycle)。来电

rides.add(car);

是完全合法的:rides.add(...) 期待 TickInterface 类型的东西,而你给它一个 Car:编译器再次确信每个 Car 实例也是 TickInterface 的一个实例。在此版本中,您已将向上转换移至代码中的这一点,而不是移至 car.

的赋值

现在,因为car的编译时类型是Car,所以你要写的方法:

void refuelCar(){
    car.refuel(); 
}

将编译和执行得很好。