假如,有一天小明接到了一个任务,该任务要他做出一个展示鸟的种类和叫声的项目,小明转身一想,用传统的OO思想毫不犹豫的写出了如下的抽象类:
public abstract class Bird {
public abstract void quack();//叫的方法
public abstract void display();//展示的方法
}
并写出了一个子类实现:
package test;
public class RedBird extends Bird{
@Override
public void quack() {
System.out.println("gua gua~");
}
@Override
public void display() {
System.out.println("redbird");
}
}
在小明洋洋自得的时候,他又接到了新的需求,让他在原有的基础上加上飞的功能,小明想,这不简单,回到桌前,瞬间在Bird类上加上了一句:
package test;
public abstract class Bird {
public abstract void quack();//叫的方法
public abstract void display();//展示的方法
public void fly(){
System.out.println("I can fly..");
}
}
小明心想这样就够了的时候,得知红鸟不会飞,于是就在RedBird上加上:
public void fly(){
System.out.println("I can't fly..");
}
写到这,小明就开开心心的将他的项目交了上去,结果,,,,被老板大骂了一顿。
我们来分析分析小明被骂的原因,第一,当小明加上飞的功能时,他直接在抽象父类加上fly的方法,如果有上百种的鸟,其中有数十种不会飞,则必须到那十几种鸟的代码中修改fly方法代码,也就是说父类挖的坑,每个子类都要填,这不是一个好的设计方法;第二,假如将fly定义为抽象方法,在子类中实现飞或者不能飞,这样就降低了代码的复用性,因为需要实现一大堆重复的代码。
当遇见设计困难时,一定要分析代码中变与不变的部分,将变的部分抽象成接口+实现来完成。分析该代码,发现fly和quack是变的,于是将他们抽象成两个接口:
package test;
public interface Fly {
void fly();//飞的方法
}
package test;
public interface Quack {
void quack();//叫的方法
}
这样抽象接口有什么好处呢?新增行为简单,可以更好的复用,没有继承带来的弊端。可以定义一系列的隶属于Fly或Quack接口的行为族,比如,定义两个类来描述飞与叫的行为:
public class redquack implements Quack{
@Override
public void quack() {
System.out.println("gua gua~");
}
}
public class redfly implements Fly{
@Override
public void fly() {
System.out.println("I can fly~");
}
}
根据这两个类,可以这样来修改Bird抽象类:
public abstract class Bird {
Fly mfly;
Quack mquack;
public abstract void display();
public Bird(){
}
public void fly(){
mfly.fly();
}
public void quack(){
mquack.quack();
}
}
因此,修改RedBird为:
package test;
public class RedBird extends Bird{
public RedBird(){
mfly=new redfly();
mquack=new redquack();
}
@Override
public void display() {
System.out.println("redFly~~~");
}
}
这样,行为的实现不在具体的类里面实现了,没有牵一发而动全身,行为的实现和类没有必需的关联,降低了耦合度,形成了一个行为的组合。由此引发出一种设计模式:
策略模式:分别封装行为接口,实现算法族,父类里面放行为接口对象,在子类中具体设计行为对象,它的原则在于:分离变化部分,封装接口,基于接口编程实现各种功能。
这就实现了行为算法的变化独立于算法的使用者。