0

I have two abstract classes: Char and Weapon. Each has two derivative classes: King and Troll, and Club and Sword.

A character always has a weapon, but the type is not specified. So when building the Char class I cannot initialise the correct type. Also, when choosing a character, I similarly cannot initialise the correct type.

Is it wrong to initialise an abstract class? How can one initialise a class of one sort and then change the variable class? Provided the new type is a trivially different inheritation of the same parent class? Or should one go about it completely differently?

It may very well be that I haven't even understood the concept of an abstract class. I'm new to Java and pure OOP.

public class Player{
    private Char c;                   // Wrong?

    public void changeChar(int charID, int wepID){
        switch(charID){
            case 1: c = new King(wepID); break;
            case 2: c = new Troll(wepID); break;
        }
    }

    public void fight(){
        c.fight();
    }
}

abstract class Char{
    protected String name;
    public Weapon weapon;         // Wrong?

    Char(int wepID){
        switch(wepID){
            case 1: weapon = new Sword(); break;
            case 2: weapon = new Club(); break;
        }
    }

    public void fight(){
        weapon.useWeapon(name);
    }
}

abstract class Weapon{
    protected String wepName;
    public void useWeapon(String user){
        System.out.println(user + " fights with " + wepName);
    }
}

class Club extends Weapon{
    Club(){
        wepName = "Club";
    }
}

class Sword extends Weapon{
    Sword(){
        wepName = "Sword";
    }
}

class Troll extends Char{
    Troll(int wepID){
        super(wepID);
        name = "Troll";
    }
}

class King extends Char{
    King(int wepID){
        super(wepID);
        name = "King";
    }
}
Felix
  • 2,548
  • 19
  • 48
  • even your question is not clear – Ravi Oct 08 '17 at 10:31
  • @Ravi Let me try to rephrase and edit. – Felix Oct 08 '17 at 10:34
  • I don't know what this sentence means: *Is it wrong to initialise an abstract class? How can one initialise a class of one sort and then change the variable class?* – M. le Rutte Oct 08 '17 at 10:46
  • An abstract class - *Char* has a variable of type *Weapon* - another abstract class. At runtime, an actual weapon type, inherited from *Weapon* is designated to the variable. This seems to work in code, but is it 'good practice', or can it backfire? If so, what could be the alternative? – Felix Oct 08 '17 at 10:50
  • Using `public` as access modifier for your instance variable *will* backfire, using `int` to determine the type of weapon or character *will* backfire, not having correct constructors in your abstract classes *will* backfire. Passing a string in `useWeapon` *will* backfire. – M. le Rutte Oct 08 '17 at 10:54
  • @M.leRutte The question wasn't about them, was it? Although thanks for the pointers. I'm just confused by the implicit conversion happening between an initialised abstract class variable, and assigning a different class to it. – Felix Oct 08 '17 at 10:57

2 Answers2

2

You cannot instantiate an abstract class. A smarter way to go is to provide your constructor for Char a Weapon instance as a parameter - you could simply use it in Char.

abstract class Char{
    protected String name;
    public Weapon weapon;

    Char(Weapon weapon){
        this.weapon = weapon;
    }

    public void fight(){
        weapon.useWeapon(name);
    }
}

class Troll extends Char{
    Troll(Weapon weapon){
        super(weapon);
        name = "Troll";
    }
}

and later in your code:

Char troll = new Troll (new Club());
Assafs
  • 3,257
  • 4
  • 26
  • 39
  • "You cannot initialize an abstract class"? What do you mean by that? An abstract class can have a constructor like any other. Or did you mean instantiate? – M. le Rutte Oct 08 '17 at 10:41
  • The code works however. So it seems one in fact "can" initialise an abstract class, and even designate an inherited, non-abstract version to it. – Felix Oct 08 '17 at 10:43
  • @Felix, you're right - I corrected my answer, I meant instantiate. You cannot create an instance of an abstract class - only of the classes which complete its implementation. for more details on this, have a read here: https://stackoverflow.com/questions/4579305/can-we-instantiate-an-abstract-class. Did my answer help you with this implementation? Does it make sense? – Assafs Oct 08 '17 at 10:45
  • @Assafs This helps, yes, thank you for the answer. And I do understand that one cannot intantiate an abstract class. But why can one initialise it and then implicitly change the class type at runtime? Is this every-day knowledge and is it used like I am using it? – Felix Oct 08 '17 at 10:52
  • Also sidenote, in 'this.param' - 'this' is optional, right? – Felix Oct 08 '17 at 10:54
  • In your original implementation you built a constructor which get an int, parses the int and then chooses a constructor to create the instance. That's not best practice if you consider future code upgrades, in which you would like to support more than 2 weapons - and you will need to have a large "case" statement to funnel to the correct constructor. It's much easier to choose the weapon when you instantiate your Char extending class - this is the point in the code that you would supply your int key anyway, so you know exactly which weapon you actually need. – Assafs Oct 08 '17 at 10:57
  • this.weapon = weapon is important, and it's not the same as weapon=weapon. It's to do with the scope of the method you're in, that has both a class variable and a method parameter with the same name. – Assafs Oct 08 '17 at 10:58
  • @Assafs I feel like a broken record, but. Apart from the obvious inconveniences in the program: is it correct to initialise an abstract class and then assign a different class to it? Is it common practice (given another situation perhaps)? – Felix Oct 08 '17 at 11:00
  • I guess the fundamental question is, does any function that accepts a parent (abstract or otherwise) also accept any children of that class? – Felix Oct 08 '17 at 11:04
  • 1
    @Felix, I understand better now. To your question: it's possible (look at the thread I directed in my previous comment). It's not best practice, as it's difficult to maintain - but if a scenario mandates that you leave the selection of which class to instantiate to the constructor of the abstract class, you can do it. Any function that gets a certain type as a parmeter will get any type that extends from it - it's a fundamental of oop. – Assafs Oct 08 '17 at 11:06
2

In Java, abstract classes are allowed to have one or more methods that are specified, but not implemented. They are meant to be used as "the highest common denominator" for subclasses that share majority of functionality, except a few methods. Therefore, Java prohibits you from creating an instance of an abstract class, and requires any subclass to provide implementations of abstract methods. This is by design. The basic idea behind it is to promote code reusability, but in real life, it's a mix of trade-offs so your mileage might vary. Most of the time, you can achieve a similar effect with a combination of non-abstract base classes and interfaces.

Based on your example, Char does not have to be an abstract class, but Weapon is a good candidate because it has a "useWeapon" method. You can declare "useWeapon" as abstract in Weapon, and provide different implementations in Sword and Club. Alternatively, you could make a Weapon an interface. The abstraction you want to make here is that a Char can use a Weapon without knowing whethere the actual weapon is a Club or Sword. This means you can give a Char anytime a weapon of any kind, and everything should work. Instead of passing a Char an "int" in constructor, it's actually better to pass it the Weapon itself.

jurez
  • 4,436
  • 2
  • 12
  • 20