user8679712
user8679712

Reputation:

Prevent certain objects from being added to a ArrayList

I was reading Eric Lippert blog about Wizards and Warriors. Interesting read, but I found certain parts hard to understand (not the authors fault, I'm only a beginner in OOP).

He presents the problem of two character types within a game, a Wizard and a Warrior, and the rules are:

In the blog, he uses a getter/setter in the first part to handle the weapon for the character, but let's change it to an inventory system. So, we have an abstract class called Player with a list of items(ArrayList).

interface Weapon {
  attack(Enemy enemy);
}

public class Staff implements Weapon {} 

public abstract class Player {
    private List<Weapon> weaponInventory;

    //left out constructor and other methods to keep it on point

    abstract void add(Weapon add)
}

and use it like so:

public class Wizard extends Player {

   @Override
   public void add(Weapon add){
      //code to add weapon;
   }
}

How would you structure the add method to enforce the rule that a Wizard can only use a staff? I thought of calling getClass() or getType() on weapon but those are considered bad practice.

The best answer I could come up with was have a String variable called type, and a getter in the Weapon interface. During object construction, set the type to sword or staff. However, this doesn't really help, as you could create a sword object, pass in staff as the type, and use it.

How would you prevent a sword from being added to the wizards inventory?

Upvotes: 2

Views: 250

Answers (3)

Eric Lippert
Eric Lippert

Reputation: 660138

How would you structure the add method to enforce the rule that a Wizard can only use a staff?

Either you didn't read the whole series, or you didn't understand its central message. The entire point of that series of articles is to express that there is no good way to structure a method that enforces a restriction on the relationship between subclasses.

This is simply a fact about subclass-based OOP; there are some things it does not model well, and this is one of them. A fundamental principle of many OOP type systems, including that of Java and C#, is Liskov's: that you can always use an instance of a subclass where an instance of a superclass is needed. That makes modeling restrictions in the type system difficult.

In the last part of the series I give the best solution that I know of: model the rules of the simulation as objects themselves, and have a rule enforcer class that gives the user feedback when they attempt to violate a rule. Don't make the type system try to solve your business problems.

Upvotes: 3

Kr&#246;w
Kr&#246;w

Reputation: 315

You could use generics:

Weapon and Staff classes remain the same:

public interface Weapon {
   void attack(Enemy enemy);
}

public class Staff implements Weapon {

   @Override
   public void attack(Enemy enemy) {
    //Do ur attacking. :)
   }

}

The Player class has a generic type:

import java.util.ArrayList;
import java.util.List;

public abstract class Player<T extends Weapon> {
    protected List<T> weaponInventory = new ArrayList<>();//Made protected so Wizard can access it.

    public abstract void add(T weapon);

}

And the Wizard class extends Player<Staff> (NOT just Player):

public class Wizard extends Player<Staff> {

    @Override
    public void add(Staff weapon) {
        // Add the staff to the list declared in Player
        weaponInventory.add(weapon);
    }

}

Explanation:

The T in Player<T> is the type of weapon that you want the player to use.

When you extend Player<Staff> in the Wizard class, you're saying that you want Wizard to be a Player that only uses Staffs. This way, the Wizard's weaponInventory list will contain only Staffs.

When you add the Warrior class, it would extend Player<Sword>, which would make its weaponInventory only take Swords.

By the way, I instantiated weaponInventory in the above code and implemented the add method in Wizard.

Upvotes: 0

dave
dave

Reputation: 11975

You could use something like the following. Note: in the Player class, the weapons can be of any type. However each sub-class of player has its own specific add(). So while this approach enforces the required rules, it loses a little generality.

public class Staff implements Weapon {} 
public class Sword implements Weapon {} 

public abstract class Player {
    private List<Weapon> weaponInventory;
    protected final void addWeapon(Weapon weapon) {
         weaponInventory.add(weapon)
    }
 }

 public class Wizard extends Player {
     public void add(Staff staff) {
         addWeapon(staff);
     }
 }

 public class Warrior extends Player {
     public void add(Sword sword) {
         addWeapon(sword);
     }
 }

Upvotes: 1

Related Questions