3

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:

  1. I don't have to downcast, I can store my Weapons in a List<Weapon> collection without worrying about checking the type, and then casting
  2. Any weapon that isn't Reloadable, won't have the concrete class Reload. This mean no throwing or leaving the method 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?

  • "*standard practice is you do it from the abstract class and then subclass*" - This isn't really "*standard practice*", rather it allows flexibility by not enforcing restrictions that aren't needed. In a case where you *need* a more specific type, use the more specific type. Having `Weapon` define state for reloading when reloading may not apply to all weapons is a design flaw in itself. If you want a reloadable weapon and non-reloadable weapon to be interchangeable, `Weapon` could simply specify that it can perform set of actions (not specific to, but allowing for: reload, shoot, swing, etc) – Vince Oct 20 '17 at 03:07
  • This post is so long, I don't even know what are you asking for :| Just add `WeaponActionList Weapon.GetActions()` which will return action list. You can ask each action for: `action.CanActivate(context)`. If yes, you can draw action icon on your UI. Also you can use `action.TakeAction()`. These actions are related to weapons, so don't need to pass any parameters to them. – apocalypse Oct 21 '17 at 16:09
  • First, no drawing, this is a text adventure game. Secondly, simply put, when I have a subclass specific method, I have to downcast to access it. To avoid downcasting, and subclass, can I make the Weapons immutable, and use the pattern to pass the required methods to the weapon. So for Sword, it only see/know swing. Gun will only see and know aim and shoot. –  Oct 21 '17 at 18:00
  • @S.R. *"this means that whatever methods are in my base class, they must be implemented in my subclass"* - that's a strange way to put it. In most languages the compiler won't let you not implement an abstract method or interface method in a concrete class. If you mean "must be *overridden* in my subclass", no, that's not what LSP is about. – guillaume31 Oct 23 '17 at 07:42
  • @guillaume31 - How would you explain LSP? –  Oct 23 '17 at 11:10
  • 1
    "A consumer manipulating a class X can be given any instance of a subclass Y instead without altering the program's correctness". One famous example of violation is the [Square as a subclass of Rectangle](https://stackoverflow.com/questions/1030521/is-deriving-square-from-rectangle-a-violation-of-liskovs-substitution-principle) case. – guillaume31 Oct 23 '17 at 11:58
  • @guillaume31 - Is it like using `List = myList = new ArrayList();` and being able to switch from `Arraylist` to another collection that implements `List` without changing code because the methods are implemented. –  Oct 23 '17 at 12:05
  • Depending what you mean by "switch from", yes it is, in that the program will continue to function correctly whatever the underlying implementation. – guillaume31 Oct 23 '17 at 12:08
  • @guillaume31 - When I say switch I mean from `ArrayList` to another implementation, without changing additional code because you changed the collection. –  Oct 23 '17 at 12:13
  • @guillaume31 - this is why I don't agree it should be blank, it should either know about and use it, or have no awareness that `reload` exists. However the SP might be a problem because shoot/reload are closely related. –  Oct 23 '17 at 12:14
  • Nothing in having a blank method breaks the correctness of a program. You're confusing LSP with [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle) (ISP). – guillaume31 Oct 23 '17 at 12:31
  • Even if I leave it blank, am I still in violation of the ISP? –  Oct 23 '17 at 12:32
  • You are in violation of ISP if you consider that a Sword shouldn't be aware of any form of reloading. But you aren't violating LSP. – guillaume31 Oct 23 '17 at 12:35
  • @guillaume31 - Doesn't the SP fix all this? Sword is only aware of what is available in the standardAction/attackAction collections, so reload would not be there. –  Oct 23 '17 at 13:48
  • It doesn't fix an LSP violation because there's never been one in the first place. It does avoid a possible ISP violation in `Sword`, by shifting it over to `Weapon`. – guillaume31 Oct 23 '17 at 14:04
  • In fact, if we follow the implementation described in your comment : *stardardAction.contains(action), if true, do it, if not, nothing happens*, `Weapon` arguably becomes an ISP violation machine, silencing anything that the underlying impl doesn't support. But consuming code still relies on it. Plus it's more complex and less type-safe (passing string around...) – guillaume31 Oct 23 '17 at 14:04

3 Answers3

0

With strategy you have the same "problem", when you call reloadStrategy the concrete strategy will be "reload" or "not reload".

Look this image from the head first design pattern book: ducks that can't fly implement a beahvior that represent that they can't fly.

Embri
  • 622
  • 3
  • 15
  • Not really, any weapon that can't reload, simply won't have the concrete reload. That's my point, the pattern solves my problem, but, I wondering it I'm using it right? Can I use it to avoid downcasting? –  Oct 20 '17 at 12:18
  • @S.R. if they haven't the concrete reload and weapon.reload() is called it throws a nullReferenceException..it's different if the concrete reload it's a beahvior that represent that they can't reload. you know the abstraction of a Weapon you don't know which concrete weapon is (and it's perfect). If you need to cast a Weapon abstraction it's unuseful. – Embri Oct 20 '17 at 15:52
  • The thing is they can't call reload like that, it checks a Collection to see if the Strategy is available, and if so, call it, if not, nothing. So Sword, would not have the reload strategy, or be aware of it. –  Oct 20 '17 at 15:55
0

In your question you wrote:

Also, if I have the reload method in the base class Weapon, then Sword would ignore or throw, which is wrong.

In my opinion, that's shouldn't be a wrong behavior, but the correct one.
You can consider Weapon as an Interface and, in ReloadableWeapon and NonReloadableWeapon classes, implement the methods you want to show in the Weapon class, assignignig to a "no-action" behaviour to the methods that don't match the specific case (e.g. the reload method in NonReloadableWeapon will be empty in his implementation or, better, it will launch an exception).
An other option for you could be use default methods (Java 8) to implement a default behaviour in Weapon class that launch an exception; method that will be overriden in the subclass where that has a sense.
In this case:
1. you would maintain the substituibility principle
2. you would be able to use Inheritance in another way (e.g if you want Weapon as a subclass of a more general class Item)
3. you wouldn't be forced to use istanceOf or cast

Ending, in my opinion, the Stategy pattern could be useful in your context to change behavior to the client class that would use your Weapon, but it wouldn't solve the lack of substituibility.

Jul10
  • 503
  • 7
  • 19
  • I understand what your saying, and to me all your points are valid, but using the pattern, it solves my problem of downcasting, and given that the weapon class is immutable, and you can only use the factory methods to create certain types of weapons, I don't have to throw, or leave methods empty, Immutable/Stategy solves my problems , it's just I'm wondering if I'm using it in the correct context. –  Oct 20 '17 at 12:26
  • I'm not sure to understand at all, but it seems you implemented the Strategy pattern in the correct context, but at this point I'm wondering why lose time writing the subclasses. You could build every type of weapon simply passing to Weapon's costructor the lists of attack strategies and actions allowed for the specific type you was creating. Anyway if ReloadableWeapon or Sword have his own methods (not declared in Weapon) you can't invoke them , because the objects created are simply istances of the base class. – Jul10 Oct 20 '17 at 13:33
  • My whole problem was attempting to avoid down casting and using a weapon in more than one way(A gun can be shot and reloaded) the SP solved both of my issues. My problem is, I thought I was stretching the context of which I'm using it, in addition to allowing the client to choose at `run time` what they want to do, I have the added benefit of not `casting` at all, and `Sword` doesn't have to ignore `Reload()`. With the SP `Sword` doesn't have reload or is even aware of it. –  Oct 20 '17 at 13:48
0

Given that line of code

public void standardAction(String action){} // -- Can call reload or aim here. 

one of two things --

  • either the caller knows if their weapon needs reloading, which makes the Strategy useless.

  • or they don't know it and it may be part of a normal routine to try and reload something that is not reloadable, in which case the sword.Reload() { // do nothing } option is perfectly valid.

Only the context can tell you which is true. Define who the caller is and define what the behavior of attacking means and you'll see more clearly where you need to go.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
  • standardAction is checking a list, example, stardardAction.contains(action), if true, do it, if not, nothing happens. The character interface doesn't know if the Weapon is reloadable or not, true/false, doesn't get returned. The character can try, and the internal logic will determine if it's possible –  Oct 20 '17 at 14:42
  • I see. To me that extra complexity can be beneficial if you need dynamic actions that are not known at compile type (e.g. ones that are stored in a weapon database). But introducing action list checking just so that you don't have empty methods feels like overkill. – guillaume31 Oct 20 '17 at 15:12
  • I know what you mean, ideally, I would leave the method blank, but for learning and understanding why you don't want to ideally violate LSP, I decided to implement the SP and see if it's a viable option. –  Oct 20 '17 at 15:19
  • `sword.Reload()` is not a perfectly valid option, especially since the OP specifically mentions LSP – Vince Oct 20 '17 at 23:42
  • I don't see how it breaks LSP. **I**SP then, maybe. – guillaume31 Oct 23 '17 at 07:16
  • `sword.Reload()` can be a valid design choice if you consciously decide that the abstraction over all weapons can be reloaded. – guillaume31 Oct 23 '17 at 07:37