0

Okay, so I'm starting to use Abstract classes, but I have a question : I have this abstract class Quest. My objective is to, when initializing a new Quest object, give it a random Quest type (Represented by a class extending Quest). For now my code is :

switch (r) {
    case 0:
        quest = new Bandit_Raids2();
        break;
    case 1:
        quest = new Bandit_Raids();
        break;
    case 2:
        quest = new Escort_Mission();
}

Is there any way to do this automatically, or just cleaner ?

deHaar
  • 17,687
  • 10
  • 38
  • 51
  • 1
    Possible duplicate of [At runtime, find all classes in a Java application that extend a base class](https://stackoverflow.com/questions/205573/at-runtime-find-all-classes-in-a-java-application-that-extend-a-base-class) – Jokab Feb 12 '19 at 09:28
  • As an idea it could be factory for all classes you need and than create kind of map with key number and name of class as value and then with random number call random class. Just an idea not to use reflection:) – Echoinacup Feb 12 '19 at 09:32

3 Answers3

0

I propose a different approach:

You could make your abstract class Quest an enum, and then implement the abstract methods in each enum constant (below is just an example):

public enum Quest {
    ESCORT_MISSION {
        public void start(){/* escort something */}
    },
    BANDIT_RAIDS{
        public void start(){/* raid bandits */}
    },
    BANDIT_RAIDS2{
        public void start(){/* raid bandits, again */}
    };

    public abstract void start();

    // add here other methods and/or constructors and/or fields
}

That way you could then randomly select an enum constant (which is an instance of Quest):

Quest[] values = Quest.values();
int randomIndex = ThreadLocalRandom.current().nextInt(values.length);
Quest quest = values[randomIndex];

The only downside to this is that you have to put all the implementations into a single class file, which can get quite messy, quite easily

Lino
  • 19,604
  • 6
  • 47
  • 65
  • 1
    This is probably a better solution but doesn't answer the initial question – Jokab Feb 12 '19 at 09:37
  • @Jokab By using `Quest.values()` you are looking up all the implementations of `Quest` if you think about it, though I know what you mean. This is not some runtime `ClassLoader`, file scanner magic stuff ;) – Lino Feb 12 '19 at 09:41
0

For the first part of the question, to do such thing in an automatic way will probably require reflexion but for the second part about being clean, most people will find it not that clean.

A possible solution if you agree to reduce a little the scope to "all classes that extends Quest" would be to have some static initializer method on all subclasses of Quest to register themself as a possible Quest and having a generic way to create instance for them something like

public abstract class Quest {
    private static final List<Supplier<Quest>> suppliers = new ArrayList<>();

    protected static void register(Supplier<Quest> questSupplier) {
        suppliers.add(questSupplier);
    }

    public static Quest createQuest(){
        int r = 0; // replace by the random call you want there
        return suppliers.get(r).get();
    }
}




public class BanditQuest extends Quest{
    static {
        Quest.register(() -> new BanditQuest());
    }
}
Wisthler
  • 577
  • 3
  • 13
  • @Lino except we need the list to be filled in before we create the first instance – Wisthler Feb 12 '19 at 10:41
  • You're right, but then currently your code doesn't work either, that initializer block needs to be `static`. But even then this approach may not work, because the class `BanditQuest` may never be loaded by the class loader if its not used, and thus it will never be registered – Lino Feb 12 '19 at 10:43
  • 1
    indeed, I missed that static initialization being lazy... And I don't see a way to go around it except calling explicitly all the sub-classes beforehand which bring us back to one of the other solutions already presented in the topic – Wisthler Feb 12 '19 at 11:19
-1

I would use the factory pattern.

public abstract class Quest {
    // some generic code
}

public class BanditQuest extends Quest {
    // some specific code
}

public interface Factory {
    Quest create();
}

public class BanditFactory implements Factory {
    @Override
    public Quest create() {
        return new BanditQuest();
    }
}

List<Factory> factories = new ArrayList<Factory>();
factories.add(new BanditFactory());
// add all factories here

Quest quest = factories.get(r).create();

You need to make sure that r is not bigger than your list though.

cmoetzing
  • 742
  • 3
  • 16
  • 2
    This doesn't really help with getting a random subclass of `Quest` – Jokab Feb 12 '19 at 09:36
  • Yes it does. You need to implement a factory for each subclass and add them to the list. Then the _get(r)_ will randomly select one of them. – cmoetzing Feb 12 '19 at 09:41
  • @cmoetzing then OP could aswell just use his switch case in the first place. This proposed solution would be overkill as one would not gain anything – Lino Feb 12 '19 at 09:48
  • @Lino OP asked for a possibly cleaner solution. Your argumentation would mean patterns do never gain anything? For arguments sake: lets assume there are 100 sub classes of `Quest`. Is it cleaner to maintain them in a single switch statement? You would really like to write `case 0:` to `case 100:` by hand? – cmoetzing Feb 12 '19 at 10:01
  • @cmoetzing I agree that they can be useful, but by the same argumenation, would you like to write `factories.add(new SomeQuestFactory());` 100 times? Seems rather messy too in my opinion – Lino Feb 12 '19 at 10:02
  • @Lino If you do not want to use reflections that is necessary in either solution. I am not saying it is the best way but it is certainly cleaner. You could even work around adding all by hand and add all factories in their constructor to some static map for example. – cmoetzing Feb 12 '19 at 10:07