I was reading on this site about the Liskov substitution principle. It states:
As per the LSP, functions that use references to base classes must be able to use objects of the derived class without knowing it. In simple words, derived classes must be substitutable for the base class.
According to this page, if you override a method in the base class and it does nothing or throws an exception, you're in violation of the principle.
Suppose I had an abstract class
called Weapon
, and the subclasses
ReloadableWeapon
and Sword
. ReloadableWeapon
contains a method that's unique to that class, called Reload()
. When declaring objects, standard practice is you do it from the abstract class and then subclass, like so:
Weapon rifle = new ReloadableWeapon();
Weapon sword = new Sword();
If I wanted to use the reload method
for a rifle
, I could cast
it. Based on numerous articles and textbooks, this could lead to problems later on.
Also, if I have the reload method in the base class Weapon, then Sword would ignore or throw, which is wrong.
If I wanted to avoid all that, would using the Strategy Pattern
be a viable option? Like this:
public final Weapon{
private final String name;
private final int damage;
private final List<AttackStrategy> validactions;
private final List<Actions> standardActions;
private Weapon(String name, int damage, List<AttackStrategy> standardActions, List<Actions> attacks)
{
this.name = name;
this.damage = damage;
standardActions = new ArrayList<Actions>(standardActions);
validAttacks = new ArrayList<AttackStrategy>(validActions);
}
public void standardAction(String action){} // -- Can call reload or aim here.
public int attack(String action){} // - Call any actions that are attacks.
public static Weapon ReloadableWeapon(String name, int damage){
return new Weapon(name, damage, this.constructActions(), this.constructStandardActions);
}
public static Weapon Sword(String name, damage){
return new Weapon(name, damage, this.standardSwordActions, this.swordActions);
}
//returns a List collection that contains the actions for a reloadable Weaopon. - Shoot
private List<AttackStrategy> reloadableActions(){}
//returns a List collection of standard non attack actions. - Reload
private List<Actions> standardReloadableActions(){}
//returns a List collection that contains the actions for a Sword - Swing/Strike
private List<AttackStrategy> swordActions(){}
//returns a List collection of standard non attack actions. - Sharpen
private List<Actions> standardSwordActions(){}
}
Attack Interface and Implementation:
public interface AttackStrategy{
void attack(Enemy enemy);
}
public class Shoot implements AttackStrategy {
public void attack(Enemy enemy){
//code to shoot
}
}
public class Strike implements AttackStrategy {
public void attack(Enemy enemy){
//code to strike
}
}
By having the List<AttackStrategy>
being constructed inside the Weapon class, client code can't pass in List<AttackStrategy>
not meant for a certain types of Weapons
, for example, a Sword cannot shoot bullets, and doesn't reload, if I added a grenade, it shouldn't be able to strike like a Sword(you get the idea).
I'm not asking if I've implemented the Strategy Pattern
correctly, but rather can I use the pattern
when faced with a subclass
that has a method
unique to that subclass
and I don't want to cast
it? or in other words, rather than violate the LSP, can I prohibit the use of inheritance and use the Strategy Pattern
to implement the require methods?
Notes:
The pattern solves my problem in 2 ways:
- I don't have to downcast, I can store my Weapons in a
List<Weapon>
collection without worrying about checking the type, and thencasting
- Any
weapon
that isn'tReloadable
, won't have theconcrete class
Reload
. This mean nothrowing
or leaving themethod
blank
My problem is, given the definition of the Strategy pattern, I don't believe I'm using it in that context.
From the site:
The Strategy pattern is to be used where you want to choose the algorithm to use at runtime. ...
The Strategy pattern provides a way to define a family of algorithms, encapsulate each one as an object, and make them interchangeable.
I like this approach, could I just look at it as allowing the client to choose how to use a Weapon
at runtime
, with the added benefit of immutability
, and avoiding to cast
? or Am I just stretching the definition a little too far?