Reputation: 1059
I'm trying to implement the strategy pattern in my code but I've run into something not covered in the examples I've seen. And that is when a strategy needs to reference a method contained in the parent class.
For the simple example (without a parent ref), lets use the Duck
and QuackBehaviour
example:
class RubberDuck extends Duck {
public RubberDuck() {
setQuackBehaviour(new Squeak());
}
}
class Squeak implements QuackBehaviour {
public String quack() {
return "QUACK!";
}
}
But what if we now have a duck that likes to say its own name? That would mean QuackBehaviour needs some sort of reference to the duck itself.
class PsyDuck extends Duck {
public PsyDuck() {
setQuackBehaviour(new SayName(this));
}
}
class SayName implements QuackBehaviour {
private Duck duck;
public SayName(Duck duck) {
this.duck = duck;
}
public String quack() {
return duck.getName();
}
}
This smells like bad code to me. Is this an anti-pattern? It prevents QuackBehaviour from being reused by a non-Duck object. And something about the co-dependency between the classes doesn't sit well with me.
I guess you could also pass in a Function to PsyDuck
like:
class PsyDuck extends Duck {
public PsyDuck() {
setQuackBehaviour(new SayName(() => this.getName()));
}
}
But if we start requiring multiple fields from the behaviour, then we would have to pass in many function arguments.
Or is it best to have SayName
be an abstract class and have PsyDuck
implement a concrete version?
class PsyDuck extends Duck {
public PsyDuck() {
setQuackBehaviour(new PsyDuckSayName());
}
class PsyDuckSayName extends SayName {
protected String getName() {
return PsyDuck.this.getName();
}
}
}
abstract class SayName implements QuackBehaviour {
public String quack() {
return getName();
}
protected abstract String getName();
}
The last approach seems the cleanest, but I would love some feedback and advice.
Upvotes: 2
Views: 465
Reputation: 2326
I don't think that it is per se an anti-pattern. Usually, you use the Strategy pattern to provide "logic" from outside a class (or rather, an instance), so it becomes a little clearer if you write it this way (although it is also fine to use it directly in your subclasses):
Duck duck = new WhateverDuck();
duck.setQuackBehaviour(new SayNameBehaviour(duck));
// or:
duck.setQuackBehaviour(new MeowBehaviour());
So it does not matter to the duck implementation how the quack is implemented. If the implementation needs the duck reference to perform its work - so be it. If not - fine.
Other commonly used patterns define a parameter to be expected by the "Strategy" implementation, so you could declare your quackBehaviour
e.g. as type Function<Duck, String>
. An implementation still could choose to ignore that parameter. But this makes some assumptions about possible (and impossible) implementations, and should be used with caution. In your case, I'd prefer the first variant.
Upvotes: 3