简体   繁体   English

如何在多个不同的对象上调用相同的刻度方法?

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

Trying to make JavaFX game but can't wrap my head around how to use tick method on different objects while being able to call other methods outside of interface methods. 尝试制作JavaFX游戏,但无法围绕如何在不同对象上使用tick方法,而又能够调用接口方法之外的其他方法来解决问题。 Made this simplified code for your pleasure: 编写以下简化的代码以使您高兴:

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
    }
}

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. 我想在具有相同接口的两个不同对象上调用tick(),但这导致我无法从Car对象调用refuelCar()。 And surely you shoudn't be able to refuel a bicycle. 当然,您应该不能为自行车加油。 What is the standard way of doing the update loop (tick) functionality? 执行更新循环(刻度)功能的标准方法是什么? Frustrating that I couldn't find solutions. 令人沮丧的是我找不到解决方案。

You do the logic inside the class that needs the logic. 您在需要逻辑的类中执行逻辑。

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

    }
    void refuel(){ 

    }
}

-edit- -编辑-

I obviously do not know what you are making exactly but introducing a player changes things. 我显然不知道您在做什么,但是介绍一名球员会改变事情。

I would update/tick your player class and let him know what he is driving since that makes sense. 我会更新/勾选您的玩家级别,并让他知道他在驾驶什么,因为这很有意义。 So if he is driving a Car instantiate it by Car playerCar = new Car() or if you really want to program to an interface (which is good practice in most cases) you can do. 因此,如果他驾驶的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.");
    }
  }

As you can see I got rid of TickInterface since that does not make sense anymore. 如您所见,我摆脱了TickInterface,因为那不再有意义。 The player and the AI are driving the cars so perhaps make these have interface 'Driver' with a tick or update function. 玩家和AI正在驾驶汽车,因此也许使它们具有带有滴答或更新功能的“ Driver”界面。 Then let them control the vehicle they drive. 然后让他们控制自己驾驶的车辆。 In a players case if a certain key is pressed you call that function of the car he is driving in the update/tick method that is being called from the game loop. 在玩家的情况下,如果按下某个键,则可以使用从游戏循环中调用的update / tick方法调用他正在驾驶的汽车的功能。 I hope that makes sense. 我希望这是有道理的。

You could still have a Vehicle interface with something like Drive() where you lower the fuel of the car. 您仍然可以使用带有Drive()类的Vehicle接口,在其中降低汽车燃料。 The fuel problem with your bike still remains, again the player needs to know what he is riding in order to make use of it's functionality. 自行车的燃油问题仍然存在,玩家必须再次了解自己在骑什么,才能利用其功能。 Take Grand Theft Auto, all vehicles could have the same interface, just the behavior changes. 以侠盗猎车手为例,所有车辆都可以具有相同的界面,只是行为发生了变化。 But if a GTA car needed to fuel up and a bike would not then a bike would be significantly different then a car. 但是,如果GTA汽车需要加油,而自行车不需要加油,那么自行车和汽车将大不相同。 Still both could inherit that refuel method from an interface, but the bike would display a message that it cannot be refueled, if that does the job for you then great but if it does not make sense it's more then likely bad design. 仍然两者都可以从界面继承该加油方法,但是自行车会显示一条消息,表明它无法加油,如果这样做对您有用,那就太好了,但如果没有意义,则很可能是糟糕的设计。

I also suggest you to read more about interfaces to understand them better. 我还建议您阅读有关接口的更多信息,以更好地理解它们。 Here is a great answer already . 这已经是一个很好的答案

TL;DR: you can do 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
    }
}

Explanation: 说明:

You're confusing "compile-time type" and "runtime type": the statement 您混淆了“编译时类型”和“运行时类型”:

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. 我想在具有相同接口的两个不同对象上调用tick() ,但这导致我无法从Car对象调用refuelCar()

is not true. 是不正确的。

The methods that an object actually has , ie the members of the object, are determined by the actual type of the object in memory at runtime (the "runtime type"). 对象实际具有的方法(即对象的成员)由运行时内存中对象的实际类型(“运行时类型”)确定。 This in turn is determined by the constructor that was used to create the object. 这又由用于创建对象的构造函数确定。

So when you write 所以当你写

TickInterface car = new Car();

then when this code is executed at runtime, it creates an object in memory (on the heap) of type Car . 然后在运行时执行此代码时,它将在内存中(在堆上)创建Car类型的对象。 You can think of this object as having both a tick() method and a refuel() method. 您可以认为该对象同时具有tick()方法和refuel()方法。

On the other hand, the methods the compiler will allow you to call are determined by the compile-time type : that is, the type of the reference variable used to refer to an object. 另一方面,编译器将允许您调用的方法由编译时类型决定 :即用于引用对象的引用变量的类型。

By writing 通过写

TickInterface car ;

you create a reference (called car ) of compile-time type TickInterface . 您创建一个编译时类型TickInterface的引用(称为car )。 This means that the compiler will only let you call 这意味着编译器只会让您调用

car.tick();

(because the compiler knows car is of type TickInterface , and it knows TickInterface declares a method called tick() ), but it will not let you do (因为编译器知道car的类型为TickInterface ,并且它知道TickInterface声明了一个称为tick()的方法),但它不会让您这样做

car.refuel();

because not every TickInterface instance has a method called refuel() . 因为并非每个TickInterface实例都有一个称为refuel()的方法。

When you assign a value to car with 当您为car分配值时

car = new Car();

you are performing an upcast . 您正在执行一个下调 The type of the expression on the right hand side of the = is Car , while the type of the expression on the left hand side is TickInterface . =右边的表达式的类型是Car ,而左边的表达式的类型是TickInterface Since the compiler is assured that every Car instance is also a TickInterface instance, this is perfectly legal. 由于可以确保编译器确保每个Car实例也是TickInterface实例,因此这是完全合法的。

When you add car to your list: car添加到列表中时:

rides.add(car);

you effectively create a second reference to the Car object you created. 您可以有效地创建对创建的Car对象的第二个引用。 The second reference is kept internally in the List . 第二个引用保留在List内部。 Since you declared the list to be of type TickInterface , with 由于您将列表声明为TickInterface类型,因此

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

you can think of that hidden internal reference as being of compile-time type TickInterface as well. 您也可以认为该隐藏的内部引用也属于编译时类型TickInterface

However, there is no reason for both these references to be the same type. 但是,这两个引用没有理由是同一类型。 You can do 你可以做

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();
    }
}

Now car has compile-time type Car (and bicycle has compile-time type Bicycle ). 现在, car具有编译时类型Car (而bicycle具有编译时类型Bicycle )。 The call 通话

rides.add(car);

is perfectly legal: rides.add(...) is expecting something of type TickInterface , and you are giving it a Car : the compiler again is assured that every Car instance is also an instance of TickInterface . 是完全合法的: rides.add(...)期望的是TickInterface类型的TickInterface ,并且您给它提供了Car :再次确保编译器确保每个Car实例也是TickInterface的实例。 In this version, you have moved the upcast to this point in the code, instead of to the assignment to car . 在此版本中,您已将upcast移至代码中的这一点,而不是分配给car

Now, because the compile-time type of car is Car , the method you wanted to write: 现在,由于car的编译时类型为Car ,所以您要编写的方法是:

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

will compile and execute just fine. 将编译并执行就好了。

It is bad approach, but you can : 这是不好的方法,但是您可以:

 ((Car) car).refuel();

I think you have to create interface with name like Refuel and void refuel(); 我认为您必须使用Refuelvoid refuel();类的名称创建接口void refuel(); -method. -方法。

And you have to decide: 您必须决定:

inheritance -> interface Refuel extends TickInterface and class Car implements Refuel 继承 -> interface Refuel extends TickInterface并且class Car implements Refuel

or implementation -> class Car implements Refuel,TickInterface . 实现 -> class Car implements Refuel,TickInterface

It depends of you tasks and architecture of application. 它取决于您的任务和应用程序的体系结构。

Like already mentioned, if you just want to keep your current architecture, you could always explicitly typecast the object to its subclass and easily access all of its specialized public methods in this way. 如前所述,如果您只想保留当前的体系结构,则可以始终将对象显式转换为它的子类,并以这种方式轻松访问其所有专用的公共方法。 You can also use the instanceof operator to check the instance type of the object first to ensure that you can apply the method. 您还可以使用instanceof运算符首先检查对象的实例类型,以确保可以应用该方法。 However, I also think it is bad practice to use it in your context. 但是,我也认为在您的环境中使用它是不好的做法。 Inheritance / interfaces are usually used to abstract common properties shared by the subclasses, independent from their concrete implementation. 继承/接口通常用于抽象子类共享的公共属性,而与它们的具体实现无关。 So it makes sense to share the "tick"-property, but your LoopClass should not have to care about what happens in each tick. 因此,共享“滴答”属性LoopClass ,但是LoopClass不必关心每次滴答会发生什么。

I would recommend you to outsource the control logic in separate classes (or include it in your game object instance classes if you want) and use the explicit subtypes of the objects. 我建议您将控制逻辑外包给单独的类(如果需要,也可以将其包括在游戏对象实例类中),并使用对象的显式子类型。 If your control class needs to modify both vehicles, it makes sense that it has knowledge of both ones. 如果您的控制班需要同时修改这两辆车,那么就可以理解这两辆车。 So eg your Car class could use a reference to a Bicycle object (maybe provided by an own attribute) and manipulate both objects in its tick() method if a collision occurs. 因此,例如,您的Car类可以使用对Bicycle对象的引用(可能由自己的属性提供),并在发生碰撞时在其tick()方法中操纵这两个对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM