简体   繁体   中英

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

  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. The player and the AI are driving the cars so perhaps make these have interface 'Driver' with a tick or update function. 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. I hope that makes sense.

You could still have a Vehicle interface with something like Drive() where you lower the fuel of the car. 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. 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

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.

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 . You can think of this object as having both a tick() method and a refuel() method.

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 . 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.refuel();

because not every TickInterface instance has a method called refuel() .

When you assign a value to car with

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 . Since the compiler is assured that every Car instance is also a TickInterface instance, this is perfectly legal.

When you add car to your list:

rides.add(car);

you effectively create a second reference to the Car object you created. The second reference is kept internally in the List . Since you declared the list to be of type TickInterface , with

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

you can think of that hidden internal reference as being of compile-time type TickInterface as well.

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 ). 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 . In this version, you have moved the upcast to this point in the code, instead of to the assignment to car .

Now, because the compile-time type of car is Car , the method you wanted to write:

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(); -method.

And you have to decide:

inheritance -> interface Refuel extends TickInterface and class Car implements Refuel

or implementation -> 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. 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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