1

What I want to do

I have an interface called strategy that has one method strategise. This method takes an interface called Entity as a parameter.

public interface Strategy{

  void strategise(Entity entity);

}
public interface Entity {
   void do_something();
}

I have a class called EntityImpl that implements Entity. EntityImpl has an additional methods that Entity does not.

public class EntityImpl implements Entity{
    
    void do_something()         // defined in Entity interface
    void do_something_else()    // unique to Person

}

I have another class StrategyImpl that implements strategy.

  • StrategyImpl expects a EntityImpl object to be passed as the parameter to the function it implements from Strategy : strategise.
  • This is because StrategyImpl.strategise() calls the method unique to EntityImpl which is not defined in Entity : do_something_else().
public class StrategyImpl implements Strategy {

  void strategise(Entity entity){
       entity.do_something_else();
  }
}

enter image description here

What I have tried to do

  1. If I use the code above, it won't work as entity.do_something_else() can't be found as do_something_else is unique to EntityImpl and not defined in Entity.
public class StrategyImpl Strategy{

  void strategise(Entity entity){
       entity.do_something_else();
  }
}
  1. I've tried using a EntityImpl object instead of an Entity object as the parameter definition in StrategyImpl.strategise() but this doesn't work as it thinks I'm not implementing Entity.strategise()
public class StrategyImpl Strategy{

  void strategise(EntityImpl entity){
       EntityImpl.do_something_else();
  }
}
  1. I've tried using generics and wildcards, but I'm not familiar with java.
public interface Strategy {

   void strategise((E implements Entity) entity);

}

Any help would be very appreciated

Thanks for your time!

ImtiazeA
  • 1,272
  • 14
  • 19
Mistakamikaze
  • 446
  • 3
  • 19

2 Answers2

5

Your design is a little flawed, because what if I did this?

Strategy s = new StrategyImpl();
s.strategise(new SomeOtherEntity());

This should compile, since s.strategise accepts an Entity and SomeOtherEntity is an Entity. But eventually it's StrategyImpl.strategise that's going to be run. It can only handle EntityImpl objects, not SomeOtherEntity!

You can solve this problem by adding a generic parameter to Strategy:

interface Strategy<T extends Entity>{

    void strategise(T entity);

}

class StrategyImpl implements Strategy<EntityImpl>{

    public void strategise(EntityImpl entity){
        entity.do_something_else();
    }
} 

This way, a Strategy<EntityImpl> is a different type from a Strategy<SomeOtherEntity>.

This restricts you from doing a bunch of things (that you should not do anyway), such as putting a bunch of general Strategys in a collection. Who knows what kind of Entity that particular Strategy accepts when you take a Strategy out of that collection?

You can, however, create a collection of Strategy<? super X> where X is an type of Entity. This collection can then contain Strategy<AnySuperClassOfX>. This works because pf PECS.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thank you very much! If it's not too much trouble, I was wondering if you could tell me if this design is a good idea based on the fact there is only 1 instance (among 6) of strategy that has that extra function. What should I do? – Mistakamikaze Oct 11 '20 at 04:11
3

StrategyImpl expects a EntityImpl object to be passed as the parameter to the function it implements from Strategy : strategise.

That's a big problem.

Your StrategyImpl class implements Strategy, and that implies that StrategyImpl fulfills the stated contract, which includes the notion that it has an implementation for the void strategise(Entity) method.

But, you don't have that. All you have is an implementation for void strategise(EntityImpl), and if you're fed an Entity that isn't an EntityImpl, all you can do is give up, and crash. That makes your StrategyImpl a thing that shouldn't be implements Strategy. It's writing a cheque it can't cash.

The rest of your problems stem from this fundamental design wart. You can work around it (such as the other answer, which will silently do nothing if you pass a non-EntityImpl Entity object, which seems like an incredibly stupid thing to. At the very least throw something!

But, there's a solution.

One simple solution could be: Well, doctor, it hurts when I press here! - So stop pressing there. If you're just making interfaces because you read in some book or heard from somebody that this is a good idea, well, it isn't. Stop doing it. delete your interfaces, rename your StrategyImpl class to Strategy and your EntityImpl class to Entity and just move on. You may think: "But, no! Now I can't choose other implementations later on!" - but we can already tell that is a false choice: You already can't. Your StrategyImpl class will fail (silently! Those are really bad bugs as they are hard to find!) if you ever mess around with having any other implementation if Entity other than EntityImpl, so your convoluted interfaces+classes mess can't deal with it either.

If you really do have a point to this interface+class mess you've made, there is still a solution. You need for the implements Strategy part of your type declaration to be more specific. You want to say: I implement a specific sort of Strategy: The kind that can only deal with EntityImpl objects. You can do that, too, with generics:

public interface Strategy<E extends Entity> {
    public void strategise(E entity);
}

And now you can implement your StrategyImpl class:

public class StrategyImpl implements Strategy<EntityImpl> {
    public void strategise(EntityImpl entity) {
        // feel free to call .doSomethingElse() here
    }
}

It is now in fact impossible for some code that has a Strategy variable to just call strategise(x) on it, where x is any random Entity. Nope; the generics have to match up. Which is exactly what you want: The compiler will check for you.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • Thank you very much! If it's not too much trouble, I was wondering if you could tell me if this design is a good idea based on the fact there is only 1 instance (among 6) of strategy that has that extra function. What should I do? – Mistakamikaze Oct 11 '20 at 04:13
  • Hard to say without knowing more about what these things represent, and why there is a 'StrategyImpl' and an 'EntityImpl' (Impl is a code smell), and what another implementation of Entity and/or Strategy might look like. – rzwitserloot Oct 11 '20 at 04:35