1.需求分析:
- 有一款《Duck》的游戏,游戏中存在很多的鸭子,主要会飞和叫。
- 鸭子有很多种,比如野鸭、家禽鸭、木头鸭和小黄鸭(一种放在浴室中的玩具)。
- 也许以后还会有更多的奇怪的鸭子,比如火箭鸭等等。
2.程序设计(此处主要关心fly()和quack()操作):
1.使用继承来实现对于更改操作的灵活程度:
在一个Duck
基类中考虑一个鸭子应该具有的所有的通用的功能,实现鸭子们确定的共有的功能,非共有的部分,要求交给子类实现。
存在的问题:
- 所有的子类都被强加了通用的
fly()
、quack()
等操作了。 - 对于a,为了让
WoodenDuck
等,不能fly()
和quack()
,只能在子类中重写方法,覆盖基类中的方法。 - 部分代码将在多个子类中重复。例如,
WoodenDuck
和ToyDuck
都不会飞,但要重写两次fly()
。 - 难以在运行时改变鸭子们的行为。
-
2.抽象出部分功能成为接口以实现更加灵活的更改操作:
让“某些”而不是“全部”的鸭子可飞、可叫。在此,
存在的问题: 部分代码将在多个子类中重复。所以仍然难以复用。例如
WildDuck
和HomeDuck
都会quack()
且相同,但是其代码将会重复两次。3.将抽象出的部分功能封装起来,尝试使得他们可以相互组合和替换。
⭐设计原则:找出应用中可能变化之处,把他们独立出来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。
对于本次需求,已知鸭子的
fly()
和quack()
会随着具体是什么鸭子的不同而不同。
所以应该将他们从基类Duck
里剥离。基于面向对象原则,被剥离的部分将作为 类 或者 接口 以存在。⭐设计原则:面向接口(或抽象)编程,而不是针对具体实现编程。
fly()
和quack()
是操作,他们变成 类 或者 接口 后没有属性,因此,本次需求使用接口。fly()
就此变成接口Fly
,它具体的实现是NormalFly
、BriskFly
和CanNotFly
。
此时,鸭子的 飞 和 叫 已经独立于Duck
,所以,Duck
需要委托原来的两个操作给现在的Fly
和Quack
接口:首先,在
Duck
类中添加两个 操作的实例变量,以便于后期指定操作接口的具体实现。- 在
Duck
中写好调用 操作的实例变量 中方法的方法。 - 考虑是否设置
setFlyAction()
等函数来使得鸭子能动态的改变 飞 等具体操作。
现在的Duck
:
public abstract class Duck {
protected Fly howToFly;
protected Quack howToQuack;
public abstract void display();
public void flyPerform(){ //实现fly
howToFly.flyAction();
}
public void quackPerform(){ //实现quack
howToQuack.quackAction();
}
public void setFlyAction(Fly now){
this.howToFly=now;
}
public void setQuackAction(Quack now){
this.howToQuack=now;
}
}
抽象出来的、频繁变动的接口Fly
:
public interface Fly {
public void flyAction();
}
具体的Fly
实现、真正的 飞 操作的执行者、Duck
最终委托的对象:
public class BriskFly implements Fly {
@Override
public void flyAction() {
System.out.println("BriskFly");
}
}
⭐设计原则:多用组合,少用继承。
在本次需求中鸭子的真正操作,由最终Duck
中Fly howToFly=具体的Fly对象;
决定,还可以使用Duck中提供的public void setFlyAction(Fly now);
,来动态改变具体的Fly
。而不再是使用继承来一个个重写。也许,写具体的Fly实现也会有些许麻烦,但是,我“有选择”一定比我“已经是”更好。