客户需求
/**
* 需求:
*
* 四种鸭子,每种鸭子都会显示自己的名称且都会游泳,但也有不同之处:
*
* 红头鸭(RedHeadDuck):可以用翅膀飞并且会呱呱叫
*
* 绿头鸭(MallardDuck):可以用脚飞但不会叫
*
* 橡皮鸭(RubberDuck):不会飞但会吱吱叫
*
* 诱饵鸭(DecoyDuck):既不会飞也不会叫
*
* 请用代码描述以上几种鸭子
*
*
*/
程序设计
1、直接利用继承如何?
将以上四种行为全部写到Duck这个基类中,然后子类重写飞和叫的行为。但若以后每加一种鸭子,都有一种不同行为,难道每个子类又都重写这个方法?很显然,这种方式不利于以后的扩展和维护
2、直接利用接口如何?
将飞和叫的行为从基类中抽取出来,将其分别放到飞的接口和叫的接口中,让有需要的鸭子去实现就可以了。貌似可行,可以解决一个鸭子中不会出现本身没有的行为。但若以后的鸭子越来越多,都实现这两个接口的话,那么就造成了代码复用性非常低。
3、找出程序中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
在Duck类中,每种鸭子都具备的方法是显示和游泳这两种行为,而变化的行为是飞和叫,所以,我们把这两个变化的行为分别放到两个接口中进行封装起来
-
Duck类
public abstract class Duck { public abstract void display(); public void swim() { System.out.println("在河边游泳"); } }
-
飞的行为
public interface FlyBehaviour { void fly(); }
-
叫的行为
public interface SoundBehaviour { void makeSound(); }
4、针对接口编程,而不是针对实现编程(设计原则二)
根据子类自己的行为,分别实现不同的的行为接口
-
用翅膀飞
public class FlyWithWings implements FlyBehaviour { @Override public void fly() { System.out.println("用翅膀飞"); } }
-
用脚飞
public class FlyWithFoot implements FlyBehaviour { @Override public void fly() { System.out.println("用脚飞,蜻蜓点水"); } }
-
不会飞
public class FlyNoWay implements FlyBehaviour { @Override public void fly() { System.out.println("飞不起来"); } }
-
呱呱叫
public class QuackSound implements SoundBehaviour { @Override public void makeSound() { System.out.println("呱呱叫"); } }
-
吱吱叫
public class SqueakSound implements SoundBehaviour { @Override public void makeSound() { System.out.println("吱吱叫"); } }
-
哑鸭
public class MuteSound implements SoundBehaviour { @Override public void makeSound() { System.out.println("叫不出声音,哑鸭"); } }
这样的设计,可以让飞和叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。当我们想继续新增一些行为时,既不会影响到既有的行为类,也不会影响“使用”到飞和叫行为的鸭子类。
现在鸭子类的的行为飞和叫并未定义在自己的类中,那怎样与行为类发生联系呢?
-
在Duck类中添加两个实例变量,类型为FlyBehaviour和SoundBehaviour,新加两个方法来代替之前出现在Duck类中的飞和叫的行为
private FlyBehaviour mFlyBehaviour; private SoundBehaviour mSoundBehaviour; public void performFlyBehaviour() { mFlyBehaviour.fly(); } public void performMakeSoundBehaviour() { mSoundBehaviour.makeSound(); }
-
在Duck类中对外提供动态设置行为类型
public void setFlyBehaviour(FlyBehaviour fb) { mFlyBehaviour = fb; } public void setMakeSoundBehaviour(SoundBehaviour sb) { mSoundBehaviour = sb; }
接下来我们看看子类的实现
/**
* 红头鸭 可以用翅膀飞并且会呱呱叫
*
*
*/
public class RedHeadDuck extends Duck
{
@Override
public void display()
{
System.out.println("我是红头鸭哦!");
}
}
------------------------------------------------------
/**
* 绿头鸭 可以用脚飞但不会叫
*
*
*/
public class MallardDuck extends Duck
{
@Override
public void display()
{
System.out.println("我是绿头鸭哦!");
}
}
------------------------------------------------------
/**
* 橡皮鸭 会吱吱叫但不会飞
*
*
*/
public class RubberDuck extends Duck
{
@Override
public void display()
{
System.out.println("我是橡皮鸭哦!");
}
}
------------------------------------------------------
/**
* 诱饵鸭 既不会飞也不会叫
*
*
*/
public class DecoyDuck extends Duck
{
@Override
public void display()
{
System.out.println("我是诱饵哦!");
}
}
测试代码
public class DuckTest
{
public static void main(String[] args)
{
Duck red = new RedHeadDuck();//多态
red.display();
red.setFlyBehaviour(new FlyWithWings());//多态
red.performFlyBehaviour();
red.setMakeSoundBehaviour(new QuackSound());//多态
red.performMakeSoundBehaviour();
red.swim();
Duck mallard = new MallardDuck();
mallard.display();
mallard.setFlyBehaviour(new FlyWithFoot());
mallard.performFlyBehaviour();
mallard.setMakeSoundBehaviour(new MuteSound());
mallard.performMakeSoundBehaviour();
Duck rubber = new RubberDuck();
rubber.display();
rubber.setFlyBehaviour(new FlyNoWay());
rubber.performFlyBehaviour();
rubber.setMakeSoundBehaviour(new SqueakSound());
rubber.performMakeSoundBehaviour();
Duck decoy = new DecoyDuck();
decoy.display();
decoy.setFlyBehaviour(new FlyNoWay());
decoy.performFlyBehaviour();
decoy.setMakeSoundBehaviour(new MuteSound());
decoy.performMakeSoundBehaviour();
}
}
程序这样设计后,不管后面添加多少个鸭子,多少个不同的行为,都能够不影响之前的代码,非常利于维护和扩展。下面是整个demo的UML图:
5、多用组合,少用继承(设计原则三)
每一个鸭子都有一个FlyBehaviour和一个SoundBehaviour,我们可以将鸭子的飞行和呱呱叫委托给它们代为处理。在本例中,我们将Duck类和两种行为类结合起来使用,这就是一种组合(composition);这种做法与直接利用“继承”不同的地方在于:鸭子的行为不是直接继承他们的父类,而是和适当的行为对象“组合”而来的。
知识总结
在本例中,这样设计程序,我们称之为:策略模式(Strategy Pattern)
- 定义
定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
在本例中,这里的算法族是指鸭子的行为,我们可以把它描述为“一族算法”。
-
三大设计原则
- 找出程序中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 多用组合,少用继承