找到你要的答案

Q:Separate implementations of an interface

Q:接口的单独实现

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();
    }
}

我正在寻找一个技术,有一个单一的入口点,我的接口,但在每个实现处理不同。

让我们举一个例子。

我有一些仪器接口的实现。仪器当然有共同之处(他们做音乐,做笔记和尺度)但是他们踢的很不同。

音乐家可以演奏乐器,有天赋的音乐家可以演奏几种乐器:

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();
    }
}

我找到了一个解决方案使用实例,但我不知道这是要走的路。我正在寻找一个设计模式或其他最佳实践来处理这种情况。

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();
    }
}
answer1: 回答1:

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().

在我看来,你应该在仪器中声明下面的方法:

public void play(Musician musician);

然后,您可以实现不同的每个仪器。

例如:

class Guitar implements Instrument {
    @Override
    public void play(Musician musician) {
        System.out.printf("Musician %s is playing the guitar!%n", musician.getName());
        strumWithPick();
    }
}

…凡此种种,不一而足。

在这个例子中,你会giftedmusician类意义不大,除非你决定使用组成副乐器或许多音乐家。

在后一种情况下,你的giftedmusician会有一个构造函数重载,说,收集& lt;仪器>;,而你的音乐家将只有一个单一的仪器有一个构造函数。

例如,用仪器作为抽象类,添加核心的“功能”发挥:

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);
    }
}

编辑后续问题编辑。

如果你需要实现特定的演奏技巧融入你的弹奏方法,无过载反模式和返回是一长串的反模式,你有所有的理由实现玩音乐。

是音乐家决定了他们到底想玩什么技术。

一旦在这个游戏身上,然后你可以适应游戏逻辑在音乐家的东西。getcurrenttechnique()。

answer2: 回答2:

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.

首先:你是对的,质疑是使用。它可能有一些使用案例,但每当你想使用实例,你应该退后一步,看看你的设计真的声音。

我的建议大致符合曼娜在他的回答中所说的。但从概念的角度来看,我认为,当你必须写一行类似的方向的依赖是有点奇怪

instrument.play(musician);

而不是

musician.play(instrument);

这样的短语“乐器可以演奏“我认为工具是一个方法的参数,而不是调用该方法的对象。他们是“被动的”,在这个意义上。(你的其中一个评论也与此有关,当你说“乐器类对音乐家有影响”时,这似乎不对。但这是否恰当也取决于实际使用情况。这个例子是非常人工和暗示,这可能会导致建议的解决方案,不适合在现实世界中。建模这一可能的解决方案的责任有很大不同,问题“谁知道?”,以及建模的结构是如何被使用的,在这里很难给出一个通用的答案。


然而,考虑到仪器可以发挥,这似乎是显而易见的,可以介绍一个简单的方法beplayed()在仪器接口。这已经在其他的答案中被提出,导致了一个简单的乐器演奏者界面的实现:

public class GiftedMusician implements Musician 
{
    @Override 
    public void play(Instrument instrument) 
    {
        instrument.bePlayed();
    }
}

其中一个悬而未决的问题是:

谁(以及如何)决定一个音乐家是否可以演奏乐器?

一个务实的解决办法是让音乐家知道乐器类,他可以发挥:

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());
        }
    }
}

在你的编辑,你的设计空间,开辟了一个新的自由度:你提到的工具可以用不同的方式演奏(如低音提琴,弓或手指)。这表明,它可能是适当的介绍playingtechnique类,正如曼娜在评论中说。

第一枪可能看起来像这样

interface PlayingTechnique {
    void applyTo(Instrument instrument); 
}

但这引出了两个问题:

1。仪器接口提供哪些方法?

这个问题可以用更自然的语言:有什么共同的东西吗?直觉上,有人会说:不多。他们可以发挥,已经显示出上述beplayed()方法。但这并不包括不同的技术,这些技术对于特定的类可能是非常具体的。但是,您已经提到了一些具体类可以拥有的方法:

Guitar#strumWithPick()
Bass#pluckString()
Piano#pressKey();
Contrabass#playWithBow();
Contrabass#pluckWithFingers()

所以对于playingtechnique类,可以考虑添加一些仿制药:

interface PlayingTechnique<T extends Instrument> 
{
    Class<?> getInstrumentClass();
    void applyTo(T instrument); 
}

并有不同的实现:

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();
    }
}

(注:一个人可以考虑进一步概括这一点。这大概意味着仪器接口会有多个子接口,像stringinstrument和keyinstrument和家,每提供一套合适的更具体的方法,如

StringInstrument#playWithBow()
StringInstrument#playWithFingers()

虽然技术上是可能的,这会提出的问题,如吉他是否可以发挥弓,或小提琴可能发挥的手指-但这超出了可以认真考虑的基础上的人为例子)

的giftedmusician课可以相应调整:

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());
    }
}

不过,还有第二个悬而未决的问题:

2。谁来决定(何时),playingtechique应用?

在目前的形式,一个天才的音乐家可以学习两种演奏技巧相同的乐器类:

giftedMusician.learn(new ContrabassBowPlayingTechnique());
giftedMusician.learn(new ContrabassFingersPlayingTechnique());

// Which technique will he apply?
giftedMusician.play(contrabass);

但是,无论是由音乐家作出的决定(也许是基于一些“熟练程度”,这是与每个技术相关的),或从外部,将取决于现实世界的问题,你实际上是试图解决。

answer3: 回答3:

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();
}

添加方法

void play();

到仪器接口。它的每一个实现都应该调用各自的方法,例如

public class Guitar implements Instrument {
  public void play() {
    strumWithPick();
  }
  private void strumWithPick() {
    // implementation details here
  }
}

然后giftedmusician #玩(仪器)应简化:

public void play(Instrument instrument) {
  instrument.play();
}
answer4: 回答4:

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?

添加方法无效play();没有任何的参数到你的接口。你想做的是表现多态性的行为。

So 而不是 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.

看来代码例子已经得到了其他的答案,但具体查词多态在面向对象的背景下。特别是这个问题有一些很好的答案:什么是多态性,它是什么,它是如何使用?

java  oop  interface