最近在看设计模式的书籍,将看到的知识做一个笔记,方便记忆。
这是第一篇。
简介
设计模式(Design pattern)代表了最佳的实践。设计模式是开发人员面临问题的解决方案。这些解决方案是众多开发人员经过相当长的一段时间试验和总结出来的。
什么是策略设计模式
策略模式(Strategy Pattern):定义了算法簇,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
... 好吧,太拗口了。
还是直接看例子吧。
场景:
现在有这样一个需求:现在需要用代码设计一辆多功能步兵车
。
要求如下:
- 这个车辆必须可以装载不同的武器,实现不同的攻击方式。
- 这个车可以安装不同的轮子,实现不同的移动方式。
一种不好的实现方式
代码实现:
public abstract class 多功能步兵车 {
public abstract void type();//车辆的类型
public abstract void attack(); // 攻击方式
public abstract void move(); // 移动方式
}
public class 火箭_履带步兵车 extends 多功能步兵车 {
@Override
public void type() {
System.out.print("我是火箭—履带—步兵车");
}
@Override
public void attack() {
System.out.print("我用火箭打死你");
}
@Override
public void move() {
System.out.print("我是在用履带奔驰");
}
}
public class 机枪_轮胎步兵车 extends 多功能步兵车 {
@Override
public void type() {
System.out.print("我是机枪—轮胎—步兵车");
}
@Override
public void attack() {
System.out.print("我用机枪打死你");
}
@Override
public void move() {
System.out.print("我是用轮胎在奔驰");
}
}
这种方式的设计,一开始在小标题中已经说明了,这是一种不好的实现方式。
将不确定的方法都写成abstract
。然后由子类一个一个去实现。子类按理说,可以扩展成任何形式的多功能步兵车
。完全可以满足要求。
的确,这是一种实现方式,可以满足要求。但是,它不是一种好的实现方式。
为什么说这种方式不好呢?
-
代码不复用
:如果现在有十辆车子,五辆的攻击方式一样,五辆的移动方式一样。按照这种方式,每辆车都必须单独实现自己的攻击方式和移动方式。那些相同的代码无法做到复用。后期变更需求时,会面临着海量的工作。 -
不灵活
:利用继承设计子类的行为,是在编译的时候静态决定了。如果在运行过程中,能够动态的对子类进行扩展或改动,这样最好不过了。 - 等等...
设计原则:
- 找出可能需要变化之处,把他们独立出来
*如果每次新需求一来,这里代码就要发生变化,那么这部分代码就需要被抽出来。
把变化的抽取出来,并做封装。以便以后轻易的改动和扩充此部分 * - 针对接口编程,而不是针对实现编程
针对接口编程的真正意义在于:利用编程语言多态的特性,针对超类型编程(supertype)。
public interface Animal {
}
public class Dog implements Animal{
}
// 实例化时不要使用Dog dog= new Dog();
Animal animal = new Dog();
// 更棒的是,可以将这个封装成这样
// 在运行时才指定具体的实现类
Animal animal = getAnimal();
- 多用组合,少用继承
“有一个” 可能比 “是一个” 更好。
使用继承设计子类的行为,是在编译的时候静态决定了,灵活度受到很大限制,所以我们要多用组合来替代继承。
例如现在有一个Flyable 接口
,你将一个Bird类
实现这个Flyable接口
以实现你想要的飞行功能。不如将Flyable接口
作为Bird类
的一个成员变量,以类似这种的方式将两个类组合起来,这样的程序更加灵活,弹性更大
(参考:Head Firsts设计模式)
策略模式设计
好了,按照上面的三条设计原则,我们将整个程序的结构做一次调整。
- 将变化的部分提取出来,单独封装。
attack()
和move()
会随着子类不同而有所改变。将这两个方法提取出来单独封装。
type()
作为子类共有的属性和方法,就直接由子类继承实现就好。 - 使用接口编程来替代现实编程。
增加两个接口:移动方式接口MoveBehavior
和 攻击方式接口AttackBehavior
- 使用组合来替代继承。
将移动方式接口MoveBehavior
和 攻击方式接口AttackBehavior
作为多功能步兵车
的成员变量。而不是直接由多功能步兵车
继承这两个接口。
多功能步兵车设计如下:
攻击方式接口设计如下:
移动方式接口设计如下:
代码实现
首先定义两种行为的接口:
public interface MoveBehavior {
void move(); // 车辆的移动
}
public interface AttackBehavior {
void attack(); // 武器的攻击
}
再定义 多功能步兵车 这个基类类
public abstract class 多功能步兵车 {
AttackBehavior attackBehavior; // 车辆攻击方式
MoveBehavior moveBehavior; // 车辆移动方式
// 步兵车类型,由子类实现这个共有方法
public abstract void type();
// 开始攻击
public void attack() {
attackBehavior.attack();
}
// 开始移动
public void move() {
moveBehavior.move();
}
// 设置攻击方式
public void setAttackBehavior(AttackBehavior behavior) {
attackBehavior = behavior;
}
// 设置移动方式
public void setMoveBehavior(MoveBehavior behavior) {
moveBehavior = behavior;
}
}
目前为止,基础规则都设定完毕,下面就开始按照规则创造车辆的攻击方式
和车辆的移动方式
。这里我们创建两种武器和两种移动方式:火箭攻击
和机枪攻击
、轮子移动
和 履带移动
移动方式实现类
public class 轮胎移动 implements MoveBehavior {
@Override
public void move() {
System.out.print("我是用轮胎在奔驰");
}
}
public class 履带移动 implements MoveBehavior {
@Override
public void move() {
System.out.print("我是在用履带奔驰");
}
}
攻击方式实现类
public class 火箭攻击 implements AttackBehavior {
@Override
public void attack() {
System.out.print("我用火箭打死你");
}
}
public class 机枪攻击 implements AttackBehavior {
@Override
public void attack() {
System.out.print("我用机枪打死你");
}
}
好了,现在说明都准备完毕了。
接下来让我们来组装真正的多功能步兵车。
1、火箭_履带步兵车
public class 火箭_履带步兵车 extends 多功能步兵车 {
public 火箭_履带步兵车(){ // 构造函数时,初始化武器了移动方式
attackBehavior = new 火箭攻击();
moveBehavior = new 履带移动();
}
@Override
public void type() {
System.out.print("我是火箭—履带—步兵车");
}
}
2、机枪_轮胎步兵车
public class 机枪_轮胎步兵车 extends 多功能步兵车 {
public 机枪_轮胎步兵车(){ // 构造函数时,初始化武器了移动方式
attackBehavior = new 机枪攻击();
moveBehavior = new 轮胎移动();
}
@Override
public void type() {
System.out.print("我是机枪—轮胎—步兵车");
}
}
现在我们有了两个定制性很强的步兵车了。
将相同的移动方式或者相同的攻击方式也提取出来,进行了复用。
而且,这时使用组合的优势就体现出来了。
我们在多功能步兵车
类中预留了两个方法:
// 设置攻击行为
public void setAttackBehavior(AttackBehavior behavior) {
attackBehavior = behavior;
}
// 设置移动方式
public void setMoveBehavior(MoveBehavior behavior) {
moveBehavior = behavior;
}
即使是在机枪_轮胎步兵车
或者火箭_履带步兵车
已经实例化完成后,
你也可以调用上面的两个方法,随时改变他的移动方式和攻击方式。
如果将多功能步兵车
直接实现接口MoveBehavior
和AttackBehavior
,会很难有这样灵活的变动。
策略模式定义
看完了上面的一大坨东西,咱们再次来说说策略模式的定义吧。
策略模式(Strategy Pattern):定义了算法簇,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
看了一个例子和两遍概念的定义,你是不是对这个设计模式有了一定的理解?
这篇笔记中还提到了一些设计原则,这些设计原则可不仅仅在策略模式中有用,在其他设计模式中,或者你平时写代码时都有很大的帮助。请牢记他们。(.)
第一篇笔记就写到这里吧!