Suppose I have a class hierarchy in Java:
interface Item { ... };
class MusicBox implements Item { ... };
class TypeWriter implements Item { ... };
class SoccerBall implements Item { ... };
and I have another class in the same package:
class SpecialItemProcessor {
public void add(Item item)
{
/* X */
}
}
where I want to do something different for each item type, but I don't want to define that action in the different Item
classes (MusicBox
, TypeWriter
, SoccerBall
).
One way to handle this is:
class SpecialItemProcessor {
public void add(Item item)
{
if (item instanceof MusicBox)
{
MusicBox musicbox = (MusicBox)item;
... do something ...
}
else if (item instanceof MusicBox)
{
TypeWriter typewriter = (TypeWriter)item;
... do something ...
}
else if (item instanceof SoccerBall)
{
SoccerBall soccerball = (SoccerBall)item;
... do something ...
}
else
{
... do something by default ...
}
}
}
This works but it seems really clunky. Is there a better way to do this, when I know of special cases? (obviously if Item
contains a method doSomethingSpecial
then I can just call that item's method without caring what type it is, but if I don't want that differentiation to occur within the item itself how do I deal with it?)
In Java you can do multiple dispatch with a visitor(-like) pattern. The Item implementations don't need to contain the processing logic, they just need an accept()
type of method.
public interface Item {
/** stuff **/
void processMe(ItemProcessor processor);
}
public interface ItemProcessor {
void process(MusicBox box);
void process(SoccerBall ball);
//etc
}
public class MusicBox implements Item {
@Override
public void processMe(ItemProcessor processor) {
processor.process(this);
}
}
public class ItemAddingProcessor implements ItemProcessor {
public void add(Item item) {
item.processMe(this);
}
@Override
public void process(MusicBox box) {
//code for handling MusicBoxes
//what would have been inside if (item instanceof MusicBox) {}
}
//etc
}
I think I'm going to use the idea of inversion of control and the visitor pattern:
interface Item {
public void accept(Visitor visitor);
...
public interface Visitor {
public void visit(Item item);
}
}
class MusicBox implements Item {
public interface Visitor extends Item.Visitor {
public void visitMusicBox(MusicBox item);
}
...
@Override public accept(Item.Visitor visitor)
{
if (visitor instanceof MusicBox.Visitor)
{
((MusicBox.Visitor)visitor).visitMusicBox(this);
}
}
}
class TypeWriter implements Item {
public interface Visitor extends Item.Visitor {
public void visitTypeWriter(TypeWriter item);
}
...
@Override public accept(Item.Visitor visitor)
{
if (visitor instanceof TypeWriter.Visitor)
{
((TypeWriter.Visitor)visitor).visitTypeWriter(this);
}
}
}
class SoccerBall implements Item {
public interface Visitor extends Item.Visitorr {
public void visitSoccerBall(SoccerBall item);
}
...
@Override public accept(Item.Visitor visitor)
{
if (visitor instanceof SoccerBall.Visitor)
{
((SoccerBall.Visitor)visitor).visitSoccerBall(this);
}
}
}
and then do the following, which at least reduces the instanceof
to one check per add()
call:
class SpecialItemProcessor
implements
MusicBox.Visitor,
TypeWriter.Visitor,
SoccerBall.Visitor,
Item.Visitor
{
public void add(Item item)
{
item.accept(this);
}
@Override public void visitMusicBox(MusicBox item)
{
...
}
@Override public void visitTypeWriter(TypeWriter item)
{
...
}
@Override public void visitSoccerBall(SoccerBall item)
{
...
}
@Override public void visit(Item item)
{
/* not sure what if anything I should do here */
}
}
Why not define some callback function to Item interface?
public Interface Item {
void onCallBack();
}
Then in each class that implements Item, such as MusicBox, it should implement the callback function.
public class MusicBox {
@override
public void onCallBack() {
// business logic
...
...
}
}
Then you could create a dispatcher, which you name is "SpecialItemProcessor".
public SpecialItemProcessor {
private final Item _item;
public SpecialItemProcessor(Item item) {
_item = item;
}
public dispatch() {
_item.onCallBack()
}
}
And then, in the Client class which contains the SpecialItemProcessor could just call the method, like:
public void XXXX() {
....
SpecialItemProcessor specialItemProcessor = new SpecialItemProcessor(new MusicBox());
specialItemProcessor.dispatch();
....
}
Actually, in C++, this is Dynamic Binding. And this is why pure abstract class exists...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With