//橡皮鸭不会飞,但会吱吱叫,所以只实现接口QuackBehavior
publicclassRubberDuckextendsDuckimplementsQuackBehavior{
//橡皮鸭叫声为吱吱叫
publicvoidquack(){
System.out.println("Squeak");
}

//橡皮鸭显示为黄头
publicvoiddisplay(){
System.out.println("Yellowhead.");
}
}

  上述代码虽然解决了一部分问题,让子类型可以有选择地提供一些行为(例如fly()方法将不会出现在橡皮鸭中).但我们也看到,野鸭子MallardDuck.java和红头鸭子RedHeadDuck.java的一些相同行为代码不能得到重复使用。很大程度上这是从一个火坑跳到另一个火坑。

  在一段程序之后,让我们从细节中跳出来,关注一些共性问题。不管使用什么语言,构建什么应用,在软件开发上,一直伴随着的不变的真理是:需要一直在变化。不管当初软件设计得多好,一段时间之后,总是需要成长与改变,否则软件会死亡。

  我们知道,继承在某种程度上可以实现代码重用,但是父类(例如鸭子类Duck)的行为在子类型中是不断变化的,让所有子类型都有这些行为是不恰当的。我们可以将这些行为定义为接口,让Duck的各种子类型去实现,但接口不具有实现代码,所以实现接口无法达到代码复用。这意味着,当我们需要修改某个行为,必须往下追踪并在每一个定义此行为的类中修改它,一不小心,会造成新的错误。

  设计原则:把应用中变化的地方独立出来,不要和那些不需要变化的代码混在一起。这样代码变化引起的不经意后果变少,系统变得更有弹性。

  按照上述设计原则,我们重新审视之前的Duck代码。

  1)分开变化的内容和不变的内容

  Duck类中的行为fly(),quack(),每个子类型可能有自己特有的表现,这是所谓的变化的内容。

  Duck类中的行为swim()每个子类型的表现均相同,这是所谓不变的内容。

  我们将变化的内容从Duck()类中剥离出来单独定义形成接口以及一系列的实现类型。将变化的内容定义形成接口可实现变化内容和不变内容的剥离。其实现类型可实现变化内容的重用。这些实现类并非Duck.java的子类型,而是专门的一组实现类,称之为"行为类"。由行为类而不是Duck.java的子类型来实现接口。这样,才能保证变化的行为独立于不变的内容。于是我们有:

  变化的内容:

//变化的fly()行为定义形成的接口
publicinterfaceFlyBehavior{
voidfly();
}

//变化的fly()行为的实现类之一
publicclassFlyWithWingsimplementsFlyBehavior{
publicvoidfly(){
System.out.println("I'mflying.");
}
}

//变化的fly()行为的实现类之二
publicclassFlyNoWayimplementsFlyBehavior{
publicvoidfly(){
System.out.println("Ican'tfly.");
}
}

//变化的quack()行为定义形成的接口
publicinterfaceQuackBehavior{
voidquack();
}

//变化的quack()行为实现类之一
publicclassQuackimplementsQuackBehavior{
publicvoidquack(){
System.out.println("Quack");
}
}

//变化的quack()行为实现类之二
publicclassSqueakimplementsQuackBehavior{
publicvoidquack(){
System.out.println("Squeak.");
}
}

//变化的quack()行为实现类之三
publicclassMuteQuackimplementsQuackBehavior{
publicvoidquack(){
System.out.println("<<Slience>>");
}
}