单独的接口实现

时间:2015-06-24 10:58:53

标签: java oop interface

我正在寻找一种技术,为我的界面提供一个入口点,但每个实现的处理方式都不同。

让我们举个例子。

我有一些Instrument接口的实现。当然乐器有一些相似之处(他们制作音乐,与音符和音阶有关),但演奏方式却截然不同。

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

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

我找到了使用instanceof的解决方案,但我不确定这是否可行。我正在寻找一种设计模式或其他最佳实践来处理这种情况。

编辑: 这个例子当然很简单,让它变得不那么明显了。因为,正如我所说,有许多种乐器,它们以不同的方式播放。就像一个低音提琴。我如何实现常规和低音提琴的Musician

public class Contrabass implements Instrument{
    public void play(boolean useBow) {
         if(useBow)
             playWithBow();
         else 
             pluckWithFingers();
    }
}

4 个答案:

答案 0 :(得分:2)

在我看来,您应该在Instrument中声明以下方法:

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或许多与Instrument相关联,否则您的Musician类会毫无意义。

在后一种情况下,您的GiftedMusician会有一个构造函数重载,例如Collection<Instrument>,而您的Musician只会有一个Instrument的构造函数。

例如(将Instrument作为抽象类,将核心“功能”添加到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);
    }
}

编辑跟进问题编辑。

如果你需要将特定的播放技术参数化到play方法中,没有过载反模式并返回instanceof长列表反模式,你就会有更多的理由使用play参数化Musician

毕竟,Musician决定了他们想要玩的技术。

一旦进入play主体,您就可以将播放逻辑调整为musician.getCurrentTechnique()行中的某些内容。

答案 1 :(得分:1)

首先:在质疑instanceof的使用时你是对的。 可能有一些用例,但每当你想要使用instanceof时,你应该退一步检查你的设计是否真的合理。

我的建议大致与Mena said in his answer的内容一致。但是从概念上看,我认为当你必须写一个像

这样的行时,依赖的方向有点奇怪。
instrument.play(musician);

而不是

musician.play(instrument);

像&#34;乐器可以播放的短语&#34;恕我直言建议仪器是方法的参数,调用该方法的对象。在这个意义上,他们是被动的#34; (你的一条评论也与此有关,当你说&#34; 乐器类导入音乐家&#34;时,这看起来并不正确)。但这是否合适还取决于真实的用例。这个例子非常人为和具有启发性,这可能会导致对现实世界中不适合的解决方案提出建议。可能的建模解决方案在责任,问题&#34> 知道 ?&#34;以及建模结构的目标方式上存在很大差异。使用过,在这里给出一般答案很难。

但是,考虑到可以播放乐器,很明显可以在bePlayed()界面中引入一个简单的方法Instrument。这已在其他答案中提出,导致Musician界面的实现只是简单地播放乐器:

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

在你的 EDIT 中,你为设计空间开辟了一个新的自由度:你提到了乐器可以以不同的方式演奏(如低音提琴,弓或手指)。这表明,引入PlayingTechnique课程可能是恰当的,正如梅纳在评论中所说的那样。

第一枪可能看起来像这样

interface PlayingTechnique {
    void applyTo(Instrument instrument); 
}

但这提出了两个问题:

<强> 1。 Instrument界面提供哪些方法?

这个问题可以用更自然的语言表达:乐器有什么共同之处?。直觉上,人们会说:不多。它们可以播放,如上面提到的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();
    }
}

(旁注:人们可以考虑进一步概括这一点。这大致意味着Instrument接口会有几个子接口,如StringInstrumentKeyInstrument以及{{1每个提供一组适当的更具体的方法,比如

WindInstrument

虽然技术上可行,但这会引起一些问题,例如吉他是否可以用弓弹奏,或者小提琴可以用手指弹奏 - 但这超出了基于人工例子可以认真考虑的范围)

StringInstrument#playWithBow() StringInstrument#playWithFingers() 课程可以相应调整:

GiftedMusician

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

<强> 2。谁决定(何时以及如何)应用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()); } }

在目前的形式中,一位有天赋的音乐家可以为同一乐器课学习两种演奏技巧:

PlayingTechique

但是,giftedMusician.learn(new ContrabassBowPlayingTechnique()); giftedMusician.learn(new ContrabassFingersPlayingTechnique()); // Which technique will he apply? giftedMusician.play(contrabass); (可能基于某些&#34;熟练程度&#34;与每项技术相关联)或来自外部的决定是否取决于现实世界的问题您实际试图解决。

答案 2 :(得分:0)

添加方法

void play();

Instrument界面。它的每个实现都应该调用各自的方法,例如

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

然后应简化GiftedMusician#play(Instrument)

public void play(Instrument instrument) {
  instrument.play();
}

答案 3 :(得分:0)

将方法void play();添加到您的界面,不带任何参数。你想要做的是展示多态的行为。

因此,不要使用instanceof来检查每个实现,而是将其作为void play();添加到您的界面中。因此,无论何时实现接口,play()方法都可以被覆盖并专门针对给定的类实现,例如你的Bass课程。

似乎代码示例已在其他答案中给出,但在OOP的上下文中专门查找术语polymorphism。特别是这个问题有一些很好的答案:What is polymorphism, what is it for, and how is it used?