Reputation: 3209
I am looking for a technique to have a single entry point for my interface, but where each implementation is handled differently.
Let's show an example.
I have got a couple of implementations of an Instrument
-interface. Instruments ofcourse share some similarities (they make music, have something to do with notes and scales) but they are played very differently.
A Musician
can play an instrument, and a gifted musician can play several instruments:
public interface Musician {
void play(Instrument instrument);
}
public class GiftedMusician implements Musician {
@Override
public void play(Instrument instrument) {
if (instrument instanceof Guitar) {
play((Guitar) instrument);
} else if (instrument instanceof Bass) {
play((Bass) instrument);
} else if (instrument instanceof Piano) {
play((Piano) instrument);
}
}
public void play(Guitar guitar) {
guitar.strumWithPick();
}
public void play(Bass bass) {
bass.pluckString();
}
public void play(Piano piano) {
piano.pressKey();
}
}
I have found a solution using instanceof
but I am not sure if this is the way to go. I am looking for a design pattern or otherwise best practice to handle such a scenario.
Edit:
This example was of course very simple, let's make it a little less obvious. Because, as i said, there are many many kinds of instruments, which are played in different ways. Like a contrabass. How would I implement a Musician
that plays regular- and contrabass?
public class Contrabass implements Instrument{
public void play(boolean useBow) {
if(useBow)
playWithBow();
else
pluckWithFingers();
}
}
Upvotes: 1
Views: 225
Reputation: 54709
First of all: You're right when questioning the use of instanceof
. It may have some use-cases, but whenever you feel tempted to use instanceof
, you should take a step back and check whether your design is really sound.
My suggestions are roughly in line with what Mena said in his answer. But from a conceptual view, I think that the direction of the dependency is a bit odd when you have to write a line like
instrument.play(musician);
instead of
musician.play(instrument);
A phrase like "an instrument can be played" IMHO suggests that the instruments are a parameter of a method, and not the object that the method is called on. They are "passive", in that sense. (One of your comments was also related to this, when you said that "the Instrument-class has an import on Musician", which doesn't seem right). But whether or not this is appropriate also depends on the real use-case. The example is very artificial and suggestive, and this may lead to suggestions for solutions that don't fit in the real world. The possible solutions for modeling this largely vary in the responsiblities, the question "Who knows what?", and how the modeled structures are intended to be used, and it's hard to give a general answer here.
However, considering that instruments can be played, it seems obvious that one could intruduce a simple method bePlayed()
in the Instrument
interface. This was already suggested in the other answers, leading to an implementation of the Musician
interface that simply plays the instrument:
public class GiftedMusician implements Musician
{
@Override
public void play(Instrument instrument)
{
instrument.bePlayed();
}
}
One of the open issues is:
Who (and how) decides whether a musician can play the instrument?
One pragmatic solution would be to let the musician know the instrument classes that he can play:
public class GiftedMusician implements Musician
{
private final Set<Class<?>> instrumentClasses =
new LinkedHashSet<Class<?>>();
<T extends Instrument> void learn(Class<T> instrumentClass)
{
instrumentClasses.add(instrumentClass);
}
void drinkLotsOfBeer()
{
instrumentClasses.clear();
}
@Override
public void play(Instrument instrument)
{
if (instrumentClasses.contains(instrument.getClass())
{
instrument.bePlayed();
}
else
{
System.out.println("I can't play the " + instrument.getClass());
}
}
}
In your EDIT, you opened a new degree of freedom for the design space: You mentioned that the instruments can be played in different ways (like the contrabass, with bow or fingers). This suggests that it may be appropriate to introduce a PlayingTechnique
class, as Mena also said in the comments.
The first shot could look like this
interface PlayingTechnique {
void applyTo(Instrument instrument);
}
But this raises two questions:
1. Which methods does the Instrument
interface offer?
This question could be phrased in more natural language: What do Instruments have in common?. Intuitively, one would say: Not much. They can be played, as already shown in the bePlayed()
method mentioned above. But this does not cover the different techniques, and these techniques may be highly specific for the particular class. However, you already mentioned some methods that the concrete classes could have:
Guitar#strumWithPick()
Bass#pluckString()
Piano#pressKey();
Contrabass#playWithBow();
Contrabass#pluckWithFingers()
So regarding the PlayingTechnique
class, one could consider adding some generics:
interface PlayingTechnique<T extends Instrument>
{
Class<?> getInstrumentClass();
void applyTo(T instrument);
}
and have different implementations of these:
class ContrabassBowPlayingTechnique
implements PlayingTechnique<Contrabass> {
@Override
public Class<?> getInstrumentClass()
{
return Contrabass.class;
}
@Override
public void applyTo(Contrabass instrument)
{
instrument.playWithBow();
}
}
class ContrabassFingersPlayingTechnique
implements PlayingTechnique<Contrabass> {
@Override
public Class<?> getInstrumentClass()
{
return Contrabass.class;
}
@Override
public void applyTo(Contrabass instrument)
{
instrument.pluckWithFingers();
}
}
(Side note: One could consider generalizing this even further. This would roughly mean that the Instrument
interface would have several sub-interfaces, like StringInstrument
and KeyInstrument
and WindInstrument
, each offering an appropriate set of more specific methods, like
StringInstrument#playWithBow()
StringInstrument#playWithFingers()
While technically possible, this would raise questions like whether a Guitar may be played with the bow, or a Violin may be played with the fingers - but this goes beyond what can seriously be considered based on the artificial example)
The GiftedMusician
class could be adjusted accordingly:
public class GiftedMusician implements Musician
{
private final Set<PlayingTechnique<?>> playingTechniques =
new LinkedHashSet<PlayingTechnique<?>>();
<T extends Instrument> void learn(PlayingTechnique<T> playingTechnique)
{
playingTechniques.add(playingTechnique);
}
void drinkLotsOfBeer()
{
playingTechniques.clear();
}
@Override
public void play(Instrument instrument)
{
for (PlayingTechnique<?> playingTechnique : playingTechniques)
{
if (playingTechnique.getInstrumentClass() == instrument.getClass())
{
// May need to cast here (but it's safe)
playingTechnique.applyTo(instrument);
return;
}
}
System.out.println("I can't play the " + instrument.getClass());
}
}
Still, there is a second open question:
2. Who decides (when and how) which PlayingTechique
is applied?
In the current form, a gifted musician could learn two playing techniques for the same instrument class:
giftedMusician.learn(new ContrabassBowPlayingTechnique());
giftedMusician.learn(new ContrabassFingersPlayingTechnique());
// Which technique will he apply?
giftedMusician.play(contrabass);
But whether the decision is made by the Musician
(maybe based on some "proficiency" that is associated with each technique), or from the outside, will depend on the real-world problem that you are actually trying to solve.
Upvotes: 1
Reputation: 48444
In my opinion, you should declare the following method in Instrument
:
public void play(Musician musician);
You can then implement it differently for each instrument.
For instance:
class Guitar implements Instrument {
@Override
public void play(Musician musician) {
System.out.printf("Musician %s is playing the guitar!%n", musician.getName());
strumWithPick();
}
}
... and so on.
With this example, your GiftedMusician
class would make less sense, unless you decide to use composition to associate an Instrument
or many to a Musician
.
In the latter case, your GiftedMusician
would have a constructor overload taking, say, a Collection<Instrument>
, whereas your Musician
would only have a constructor with a single Instrument
.
For instance (with Instrument
as abstract class, to add core "functionality" to play
):
class Musician {
protected Collection<Instrument> instruments;
Musician(Instrument instrument) {
instruments = new HashSet<Instrument>();
if (instrument != null)
instruments.add(instrument);
}
public String getName() {
// of course
return "J. S. Bach";
}
}
class GiftedMusician extends Musician {
GiftedMusician(Instrument instrument) {
super(instrument);
}
GiftedMusician(Collection<Instrument> instruments) {
super(null);
this.instruments = new HashSet<Instrument>(instruments);
}
}
abstract class Instrument {
protected String name;
public void play(Musician musician) {
System.out.printf("Musician %s is playing %s%n", musician.getName(), name);
}
}
Edit following up question edit.
If you need to parametrize a specific playing technique into your play
method, without an overload anti-pattern and returning to the instanceof
long list anti-pattern, you've got all the more reason to parametrize play
with a Musician
.
It's the Musician
who decides the technique they want to play with after all.
Once within the play
body, you can then adapt the playing logic to something in the lines of musician.getCurrentTechnique()
.
Upvotes: 2
Reputation: 3381
Add the method void play();
to your interface without any parameters. What you want to do is exhibit the behaviour of polymorphism.
So instead of using instanceof
to check for each implementation, you have it in your interface as just void play();
. Hence, whenever the interface is implemented, the play()
method can be overriden and implemented specifically for the given class e.g. your Bass
class.
It seems code examples have already been given in other answers but specifically look up the term polymorphism
in the context of OOP. In particular this question has some good answers: What is polymorphism, what is it for, and how is it used?
Upvotes: 0
Reputation: 64650
Add method
void play();
to the Instrument
interface. Each implementation of it should be calling respective method, e.g.
public class Guitar implements Instrument {
public void play() {
strumWithPick();
}
private void strumWithPick() {
// implementation details here
}
}
Then GiftedMusician#play(Instrument)
should be simplified:
public void play(Instrument instrument) {
instrument.play();
}
Upvotes: 0