4

Suppose I have the following interface and implementation:

interface Weapon{
    int attack();   
}

public class Sword implements Weapon {

    //Constructor, and Weapon interface implementation
    //...
    public void wipeBloodfromSword(){}
}


public class ChargeGun implements Weapon {
    //Constructor, and Weapon interface implementation  
    //...
    public void adjustlasersight(){}
}

and Store them like this:

List<Weapon> weaponInventory = new ArrayList<Weapon>();
weaponInventory.add(new Sword());
weaponInventory.add(new ChargeGun());

Problem:

Given that they're stored in the List<Weapon> I obviously only have access to the methods declared in the Weapon interface. If downcasting and the use of instanceof/getClass() should be avoided, how would I get access the the class specific methods wipeBloodfromSword() and adjustlasersight()?

Possible Solution:

Given that there are actions before and after the attack method is called, I can re-write my interface like this:

interface Weapon{

   //Can reload a weapon, adjust a laser sight 
   //do anything to the weapon to prepare for an attack
   void prepareWeapon(); 
   int attack();
   //Not sure of a more proper name, 
   //but you can wipe blood off sword  or take off silencer 
   void postAttackActions();
}

While, I'm in control of this hobby project, I might run into a situation where I'm unable to change the interface, while the interface re-write may solve this specific problem, what should I do if I have to leave the interface as is?

  • "what should I do if..." is not a good type of question to ask on SO. – Janez Kuhar Oct 25 '17 at 00:04
  • 1
    If you can't change your interface, you *have to* use either `instanceof` or reflection. But the correct answer is to change your interface. – Andy Turner Oct 25 '17 at 00:05
  • Static class isn't a right answer??? – A Farmanbar Oct 25 '17 at 00:06
  • why should instanceof be avoided? if you store all weapons in list but want to call methods in extending classes, you will need to check the type before you attempt to cast it. otherwise, store each weapon subclass in its own list (which has its own drawbacks). or you have a common method on weapon such as "postAttackAction" which for a sword will wipeBloodfromSword and for other weapons do other things. or nothing. you just need to make design decisions around which things are "generic" and which things are handled on a class by class basis. – slipperyseal Oct 25 '17 at 00:42
  • see also: https://en.wikipedia.org/wiki/Chiburi "the process by which one symbolically removes blood from a sword blade" :) – slipperyseal Oct 25 '17 at 00:44

2 Answers2

3

Since you have a fixed set of classes, you could use the visitor pattern, which works without explicit downcasts.

class WeaponVisitor {
   void visit(Sword aSword) { }
   void visit(ChargeGun aGun) { }
}

// add accept method to your Weapon interface
interface Weapon {
  ...
  void accept(Visitor v);
}

// then implement accept in your implementing classes
class Sword {
...
   @Override
   void accept(Visitor v) {
      v.visit(this); // this is instanceof Sword so the right visit method will be picked
   }
}

// lastly, extend Visitor and override the methods you are interested in
class OnlySwordVisitor extends Visitor {
     @Override void visit(Sword aSword) {
      System.out.println("Found a sword!");
      aSword.wipeBloodfromSword();
     }
 }
Jochen Bedersdorfer
  • 4,093
  • 24
  • 26
  • Wonderful, I don't have to use `downcasting`, `instanceof`, or `getClass()` –  Oct 25 '17 at 04:28
  • If you can, change the `void accept(Visitor v)` to `void accept(WeaponVisitor v);` No need to have OnlySwordVisitor `extend` `Visitor`, I can simply use the `WeaponVisitor`, correct? –  Oct 25 '17 at 12:01
  • yes, sure. `SwordVisitor` is just an example of a visitor you could use to only act on swords. Also, if you throw in `abstract class AbstractWeapon implements Weapon` you only need to implement `accept` in that one if all concrete weapons subclass it – Jochen Bedersdorfer Oct 25 '17 at 15:32
-1

You can type cast your List items to Sword or ChargeGun and then invoke the respective methods

((Sword) weaponInventory.get(0)).wipeBloodfromSword();
hsnsd
  • 1,728
  • 12
  • 30
  • But to cast, I would have the check the type first(using `instanceof` or `getClass()`). There is no guarantee that sword will be the first item in the inventory –  Oct 25 '17 at 03:57