CLOVIS
CLOVIS

Reputation: 998

Getting an instance of a third-party class inheriting one of yours

So here's the situation : I'm creating a multiplayer game, and I want my friends, ..., to be able to create bots for it. So what I'm going to do is create an abstract class Bot{}, and define functions for the bots, then they'll have to implement them : pretty straight forward yet.

Where I'm stuck, is how do I add them afterwards ? Can you use reflection or something like that to find all inheriters of Bot ? Or do I need to search through .jar files ?

Until now, here's how I did it (simplified, classes are not in the same file, etc) :

abstract class Bot{
  public int play();
  //...
}

class Bot1 extends Bot{
  @override
  public int play(){/*do stuff*/}
  //...
}

and then when I need to have one of each bot :

ArrayList<Bot> bots = new ArrayList<>();
bots.add(new Bot1());
//...

My problem is that if I were to use this, as the Bot1 instanciation is hard-coded, I need to allow people to modify my code to add their own bots - which I don't want.

My question is, how can I achieve this without it being hard-coded ? I know it's possible, as Minecraft does it (mods and such), but I have no clue how to do it. So far, my researches didn't got me any results (probably because I have no idea how to explain this in a simple sentence...), so any clues of tips are welcome, as well as places to learn how to do it.

Also, I have a good understanding of OOP and such, but I'm totally new to reflection and getClass() things...

Upvotes: 3

Views: 190

Answers (6)

NickD
NickD

Reputation: 506

You could get your friends to create their bot classes that implement your Bot interface, then put them all in a folder and load every class in the folder using classLoader.loadClass. Use instanceof to check if each class implements the required interface and discard them if they don't. I wrote a simple plugin system to try this out earlier in the week but don't have access to the code right now, but info on how to use all these elements is readily available, good luck!

Upvotes: 1

boxed__l
boxed__l

Reputation: 1336

Excellent answer by Mick.

Here is my solution (not using the ServiceLoader API, but a similar approach).

  1. Declare a property file with property name BotClasses= followed by the fully qualified path of your friend's bot class. (for multiple classes split them by some delimiter like ;)

  2. Iterate over the BotClasses property value by splitting it with the delimiter and then for each fully qualified class name use:

Bot newObj = (Bot) Class.forName(strFullyQualifiedClassName).newInstance();

This works only if there is a default/nor-arg constructor in the Bot implementations.

Upvotes: 1

Mick Mnemonic
Mick Mnemonic

Reputation: 7956

Simplest way to do this (without any external frameworks) is probably to define Bot as an interface (SPI, Service Provider Interface) and then use the built-in Service Loader mechanism that ships with the JRE. Custom Bot implementations would then need to be deployed (as JARs) so that they are accessible on your application's classpath.

So, something like:

SPI contract:

public interface Bot {
    int play();
}

Implementation (this needs to be packaged into a JAR that has the directory structure as specified in the linked documentation, including the META-INF folder):

public class MyBot implements Bot {
    @Override
    public int play() {
        // do something
    }
}

Lookup:

import java.util.ServiceLoader;

public class BotResolver {

    private final ServiceLoader<Bot> loader;

    public BotResolver() {
        loader = ServiceLoader.load(Bot.class);
    }

    /**
     * Gets all bot implementations that are currently available in the classpath.
     */
    public List<Bot> getAllBots() {

        List<Bot> allBots = new ArrayList<>();
        Iterator<Bot> it = loader.iterator();

        while (it.hasNext()) {
           allBots.add(it.next());
        }

        return allBots;
    }
}

Upvotes: 4

Julian
Julian

Reputation: 4075

This may not be what you asked for but trying to work out all sub classes of a given class at run time seems to me not a good idea. Why not offer your friends the facility to register their Bot instances as they wish say via a singleton:

public enum BotHolder {
    BOTS;

    private final List<Bot> bots = new ArrayList<>();

    public void registerBot(Bot bot) {
        bots.add(bot);
    }

    public List<Bot> get() {
        return bots;
    }
}

Upvotes: 2

Jason
Jason

Reputation: 11832

If you are using Spring, you can request that the Bot implementations are annotated with @Component so that your application can use a component scan and then use @Autowired private List<Bot> bots; to access them.

Upvotes: 0

Max van Deursen
Max van Deursen

Reputation: 60

From what I have read in the topic How do you find all subclasses of a given class in Java?, there seems to be no real way to do it other than to "brute-force" your way through each class whilst checking whether it is a subclass of Bot.

To answer your question, it is possible and it is described in the above topic!

Upvotes: 1

Related Questions