1

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.

fabian
  • 80,457
  • 12
  • 86
  • 114
  • Wouldn't the car be able to refuel itself from within the tick method? – NickL Jan 31 '18 at 21:01
  • @NickL Well maybe, but refueling isn't the only thing I want to affect the car. It was just something I came up with. –  Jan 31 '18 at 21:55

4 Answers4

2

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, i.e. 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.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • This is very clear answer and exactly what I was looking for and helped me to understand a bit more about java and OOP. Thanks again, and other commenters too. –  Jan 31 '18 at 23:11
1

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.

Madmenyo
  • 8,389
  • 7
  • 52
  • 99
  • What if I have to call that function from outside of the Car class? What if there is other factors that impacts the car that cannot be controlled in a tick function? –  Jan 31 '18 at 21:07
  • You make the function public. – Madmenyo Jan 31 '18 at 21:09
  • Just tested and I cannot do that, because that method is not defined in the interface. –  Jan 31 '18 at 21:15
  • 1
    Why exactly do you need to run it outside? Seems to me that using an interface is not an option here. I understand you want to tick all the "Tickable" objects in your `LoopClass` but it should not be able to refuel your car. An interface is a contract, so yes a bike could "tick forward" and a car could "tick forward" but the behavior should be encapsulated. – Madmenyo Jan 31 '18 at 21:23
  • Just trying to have GameLoop in a game. There could be multiple types of moving objects that still share tick-method which enables them to i.e move forward once in every tick. Figured interface made it possible to have updating function on every class. But now I cannot call their base methods outside the classes. I want to get the car's current location with getLocation(). I wan't to lock the car from player class for example, but I can't because it is an interface type. Maybe the interface doesn't work but how do I implement GameLoop in every moving object to move or to check for collision? –  Jan 31 '18 at 21:43
  • You are doign very good to create yourself an interface for all the classes that need to get updated. However that interface only tells about updating and not about behavior. But having the game loop class have access to all the behavior of your objects is bad design. To incorporate collision you could add a class to your interface that returns a position or a rectangle. – Madmenyo Jan 31 '18 at 21:56
  • The game loop class don't have to have the behaviour of the car object. It's enough that maybe player class could i.e call for refuel() or unlockCar()-method of the car object, but is not possible because of the way the car was declared (interface type). The tick() of the car and collision detection could be working perfectly though. –  Jan 31 '18 at 22:09
  • Well, you could always declare it `Car playerCar = new Car();` but you still won't be able to run `refuel()` when you loop over it as your interface type. If your player rides the car it makes sense he has access to driving and fueling it. The player should also know if he is driving a car or a bicicly. So you call `Tick()` on your player, and have him press the pedal to the metal. I'll update my answer in a bit. – Madmenyo Jan 31 '18 at 22:13
  • Also note you can share different references - potentially with different types - to the same object. Ie your `Player` may have a reference to a `Car`: `private Car car = new Car();`, and you can add that to your list of `TickInterface` objects: `List rides = new ArrayList<>();` and then `rides.add(player.getCar())`. Now you can loop through `rides` and call `tick()` on each of them, and e.g. your `Player` can do `car.refuel();`. Both of those call methods on the same actual object. – James_D Jan 31 '18 at 22:32
  • @James_D Well this actually worked although I didn't know you could add `Car` type of object to an arraylist which is an `TickInterface` type. It's almost the same as using `((Car) car)`? Maybe it won't be too bad idea to use it this way you mentioned. Thanks. –  Jan 31 '18 at 23:03
  • @H36615 It's an *upcast*: `rides.add(...)` is expecting something of type `TickInterface`, and since a `Car` *is a* `TickInterface`, `rides.add(car)` is valid. You're already performing an upcast: `TickInterface car = ... ;` is also expecting something of type `TickInterface`, and you're providing it a `Car`: `TickInterface car = new Car();`. Again, that works because a `Car` *is a* `TickInterface`. All I did was move the place where the upcast happens. See my answer for a complete description. – James_D Jan 31 '18 at 23:10
  • @H36615 It's the sole purpose of an interface. You create a contract between different objects. You actually say "this object is a " and thus a list of these interfaces take anything that is of that interface. But if you need to do a lot of casting `(Car)vehicle` it fails to do your job since you are breaking out of that contract. Sure this works but it is a sign of bad design. – Madmenyo Jan 31 '18 at 23:10
  • OTOH, `((Car)car)` is a downcast: not every object of type `TickInterface` is an instance of `Car`, so you have to explicitly tell the compiler that this particular instance can be treated as a `Car`. To clarify @Madmenyo, lots of downcasting is a sign of a bad design (and is prone to runtime errors if you get it wrong): upcasting is not a sign of a bad design. – James_D Jan 31 '18 at 23:12
0

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.

kozmo
  • 4,024
  • 3
  • 30
  • 48
  • Would either of those methods work when I've already defined the object to be tickInterface type, which doesn't have refuel method? –  Jan 31 '18 at 21:11
  • Sorry read too quickly your comment. But I've too heard it's a bad approach to use the first line from your answer. I've wonder what really is the most optimal way to make the tick loop and -method? –  Jan 31 '18 at 21:18
  • If you define `TickInterface` reference you won't be able to use `refuel` – kozmo Jan 31 '18 at 21:18
  • I knew that but I'd like to know how could I implement updateLoop mechanism where I could have tick()-like method on multiple different type classes, while still being able to call their own public methods outside of their own classes? –  Jan 31 '18 at 21:27
  • It's a bit unclear, please update you question -> try to expand the topic. – kozmo Jan 31 '18 at 21:34
  • Sorry I might not have explained very well but check my last comment on the other answer where I tried to be more clear –  Jan 31 '18 at 21:47
0

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 instanceofoperator 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 e.g. 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.

Frederic
  • 1
  • 1