Reputation:
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
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
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);
}
}
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 Staff
s. This way, the Wizard
's weaponInventory
list will contain only Staff
s.
When you add the Warrior
class, it would extend Player<Sword>
, which would make its weaponInventory
only take Sword
s.
By the way, I instantiated weaponInventory
in the above code and implemented the add
method in Wizard
.
Upvotes: 0
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