Joost
Joost

Reputation: 3209

Separate implementations of an interface

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

Answers (4)

Marco13
Marco13

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

Mena
Mena

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

Force444
Force444

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

Boris Pavlović
Boris Pavlović

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

Related Questions