我正在寻找一种技术,为我的界面提供一个入口点,但每个实现的处理方式都不同。
让我们举个例子。
我有一些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();
}
}
答案 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
接口会有几个子接口,如StringInstrument
和KeyInstrument
以及{{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?