设计模式--引入模式雏形

接着上一篇文章中遗留的问题,接着探索一下设计模式的强大力量。
首先在上一篇文章中进行简单的回顾:

1、我们声明了一个Duck抽象类,并定义了相关的公共方法(quack、swim、fly),并且都进行了默认的实现;但是把display外观方法定义成了抽象方法,以达到不同类型的Duck可以具有自己独特外观的目的。

2、然后定义具体的业务类(绿头鸭、大黄鸭、红头鸭),该业务类要继承抽象类-Duck,并且覆写display方法,定义自己独特的外观。

那么针对上面的实现,通过分析具有下面的一系列问题:

1、比如再新加一个“跳舞鸭”业务类时,该鸭子的跳舞行为难道也要在超类Duck中定义吗?这样会导致超类很臃肿

2、如果对某个行为进行动态扩展怎么办?比如有些飞行行为是用翅膀飞行,有些飞行行为是用火箭动力飞行等等,难道我们要把那些需要火箭动力飞行的所有业务类中的fly方法都要一个一个的重新改一遍吗?

针对以上问题我们慢慢进行设计一下吧。

一、第三代鸭子–把不同的行为进行独立定义(把变化的和不变化的分开)

其实做法就是“把会变化的部分取出并封装起来,以便以后可以轻易的改动或扩展此部分,而不影响不需要变化的其它部分”,那么下面我们这么设计,把飞行的行为fly、叫的行为quack单独抽取出来,定义成为不同的业务类,这样的话以后我们基于这些行为类扩展出不同的具体行为,比如我们可以扩展fly行为(用翅膀飞行、用火箭动力飞行、静止)。

在前几代鸭子产品开发中,我们一直使用的是定义父类的方式来约束对应的行为方法,然后子类通过继承父类,即可拥有父类的方法;但是如果子类有差异化的行为时,就需要覆写父类的默认方法了;但是还有一种情况,就是子类如果定义自己独特的行为方法时,那么就只能在自己内部进行定义和实现了,因为java单重继承关系的约束 ,所以为以后扩展或者行为复用造成了麻烦。

下面我们将采用采用接口进行定义不同的行为,然后其它因为类实现具体的接口即可获得其需要的行为;归结来讲就是:“针对接口编程,而不是针对实现编程”。

1、独立定义飞行的行为接口-FlyInterface

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 独立出来的飞行的业务接口
* @author maps
*/
public interface FlyInterface {
/**
* 定义一个飞行方法
*/
public void fly();
}

1.1、定义用翅膀飞行的业务类-FlyWithWings

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义用翅膀飞行的具体类,实现飞行方法
* @author maps
*/
public class FlyWithWings implements FlyInterface {
@Override
public void fly() {
System.out.println("用翅膀飞行");
}
}

1.2、定义用火箭动力飞行的业务类-FlyRocketPowered

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义用火箭动力飞行的具体类,实现飞行接口
* @author maps
*/
public class FlyRocketPowered implements FlyInterface {
@Override
public void fly() {
System.out.println("利用火箭动力飞行");
}
}

1.3、定义不会飞行的业务类-FlyNoWay

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义用不会飞行的具体类,虽然实现了飞行的接口
* @author maps
*/
public class FlyNoWay implements FlyInterface {
@Override
public void fly() {
System.out.println("不会飞行!");
}
}

到目前为止,我们已经把飞行行为fly进行了独立定义,并且定义了不同的具体飞行行为业务类;下面我们开始独立定义另一个叫的行为quack。

2、独立定义叫的行为接口-QuackInterface

1
2
3
4
5
6
7
8
9
10
11
/**
* 独立出来的叫的行为接口
* @author maps
*/
public interface QuackInterface {
/**
* 定义一个叫的方法
*/
public void quack();
}

2.1、定义一个呱呱叫的行为类-QuackGuagua

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义一个呱呱叫的具体类,实现呱呱叫行为接口
* @author maps
*/
public class QuackGuagua implements QuackInterface{
@Override
public void quack() {
System.out.println("呱呱叫");
}
}

2.2、定义一个不会叫的业务类-QuackNoWay

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义一个不会叫的业务类
* @author maps
*/
public class QuackNoWay implements QuackInterface {
@Override
public void quack() {
System.out.println("不会叫");
}
}

2.3、定义一个吱吱叫的业务类-QuackZhiZhi

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义一个吱吱叫的业务类,实现独立叫的行为接口
* @author maps
*/
public class QuackZhiZhi implements QuackInterface {
@Override
public void quack() {
System.out.println("吱吱叫");
}
}

到目前为止,我们已经把叫的行为quack进行了独立定义,并且定义了不同的具体叫的行为业务类;其实已经可以观察到,我们把飞行fly和叫quack行为定义成独立的行为,可以让飞行和叫的动作被其他的对象复用,因为这些行为已经与具体的鸭子类无关了,而我们也可以后期新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。

2、重构定义鸭子业务类-Duck

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 定义一个鸭子的业务类,并且声明已经独立定义的飞行、叫的行为接口
* @author maps
*/
public class Duck {
FlyInterface flyInterface;
QuackInterface quackInterface;
Duck(){}
/**
* 将“叫”的行为委托给接口行为类,这样就可以在不同的业务类中初始化不同的行为类
*/
public void quack(){
quackInterface.quack();
}
/**
* 将“飞行”的行为委托给接口行为类,这样就可以在不同的业务类中初始化不同的行为类
*/
public void fly(){
flyInterface.fly();
}
}

在我们定义Duck类中,把已经定义好的两个独立行为接口作为变量引入到了类中,这样的话,接下来我们在新建Duck类的具体子类时就可以拥有父类Duck声明的接口变量,并且可以动态实例化父类Duck中的接口变量为不同的行为接口实例。

2.1、重构定义绿头鸭业务类-GreenDuck

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 绿头鸭业务类
* @author maps
*/
public class GreenDuck extends Duck {
/**
* 1、因为绿头鸭类继承了Duck,所以可以继承拥有父类中的飞行、叫两个接口实例变量(flyInterface、quackInterface)
* 2、所以我们在构造函数中将继承的接口实例变量进行初始化操作:
* 1)、把飞行接口变量初始化成“用翅膀飞行”的具体行为
* 2)、把叫的接口变量初始化成“呱呱叫”具体行为
*/
public GreenDuck(){
flyInterface = new FlyWithWings();
quackInterface = new QuackGuagua();
}
}

可以看到上面我们定义的绿头鸭业务类中分别把父类中的flyInterface接口变量实例化为用翅膀飞行的具体行为类、把父类中的接口变量quackInterface实例化为呱呱叫具体行为类。

2.2、重构定义绿头鸭业务类-RedDuck

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 红头鸭业务类
* @author maps
*/
public class RedDuck extends Duck {
/**
* 1、因为绿头鸭类继承了Duck,所以可以继承拥有父类中的飞行、叫两个接口实例变量(flyInterface、quackInterface)
* 2、所以我们在构造函数中将继承的接口实例变量进行初始化操作:
* 1)、把飞行接口变量初始化成用“火箭动力”飞行的具体行为
* 2)、把叫的接口变量初始化成“吱吱叫”具体行为
*/
RedDuck(){
flyInterface = new FlyRocketPowered();
quackInterface = new QuackZhiZhi();
}
}

3、定义一个测试类,在测试方法中测试绿头鸭和红头鸭的飞行和叫的行为

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Duck greenDuck = new GreenDuck();
greenDuck.quack();
greenDuck.fly();
System.out.println("-----------------------------");
Duck redDuck = new RedDuck();
redDuck.quack();
redDuck.fly();
}

上面测试代码输出如下:

呱呱叫

用翅膀飞行


吱吱叫

利用火箭动力飞行

通过上面的测试代码输出可以看出我们不同的Duck子类中,实例化了不同的接口行为变量,然后他们便具有了不同的行为操作。

思考一个问题,比如后续再添加其他行为时怎么办,比如添加一个“跳跃”行为时,并且有单腿跳跃和双腿跳跃具体行为,我们可以如下扩展:

定义一个“跳跃”的接口

定义一个具体的“单腿跳跃”行为类,该类实现上面定义的“跳跃”接口

定义一个具体的“双腿跳跃”行为类,该类实现上面定义的“跳跃”接口

在Duck这个父类中,添加一个“跳跃”接口类型的变量

在Duck这个父类中,再新定义一个“跳跃”的行为的方法,并且在方法中调用“跳跃”接口变量中的跳跃方法

如果我们只需要为“红头鸭”添加其中的“单腿跳跃”行为时,则只需要在红头鸭中构造方中实例化父类的跳跃接口变量为具体的单腿跳跃行为类即可

通过对前两代鸭子工程的重构和扩展,发现系统扩展性更好了!这就是设计模式的魅力所在!!后续将继续深入探索并运用他。