您的位置:首页 > 编程语言 > Java开发

Java设计模式透析--策略模式(一)

2017-05-15 09:06 260 查看
策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
知识点的梳理:
为了"复用"目的而使用继承,结局并不完美!
找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起;
针对接口编程,而不是针对实现编程;

多用组合,少用继承;
  

模拟鸭子应用

示例:公司开发了一套模拟鸭子游戏SimUDuck。游戏中会出现各种鸭子,一边游泳,一边呱呱叫。

此系统最初的设计:



现在需要让鸭子具备飞行能力

在Duck类中加上fly()方法,然后所有鸭子继承fly()即可!



出现问题,橡皮鸭子也具备了飞行能力!

在超类中加入新的行为,会使得某些并不适合该行为的子类也具有该行为!



或者,可以将橡皮鸭类中的fly()方法覆盖掉,就好像覆盖quack()的做法一样?



但是,如果以后加入木头鸭子,既不会飞也不会叫,也要覆盖相应的方法吗?



所以,条件可能常常改变,每当有新的鸭子子类出现,他就要被迫检查并可能需要覆盖fly()和quark()~~~

利用接口?

将fly()从超类中取出来,放进一个"Flyable接口"。只有会飞的鸭子实现此接口即可。也可以创建一个"quackable"接口,因为并不是所有的鸭子都会叫。



问题:重复的代码会变多!也就是说代码不能被复用!

分开变化和不会变化的部分

为了分开"变化和不会变化的部分",建立两组类(完全远离Duck类)。一个是fly相关的,一个是quack相关的。每组类将实现各自的动作



利用接口代表每个行为,比如,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。
鸭子类不负责实现Flying与Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就是"行为"类。由行为类而不是Duck类来实现行为接口;

以前的做法:
行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于"实现",代码被绑死,无法修改;

新的设计:

鸭子的子类使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实现不会被绑死在鸭子的子类中;



实现鸭子的行为

现在有两个接口,FlyBehavior和QuackBehavior,还有它们对应的类,负责实现具体的行为:



这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了;

问题:用一个类代表一个行为,似乎优点奇怪,类不是应该代表某种"东西"吗?类不是应该同时具备状态与行为吗?
类代表的东西一般都是既有状态(实例变量)又有方法,只是在本例中,碰巧"东西"是个行为。但是即使是行为,也仍然可以有状态和方法,例如,飞行的行为具有实例变量,记录飞行行为的属性(每秒翅膀拍动几下,最大高度和速度等);

整合鸭子的行为
鸭子现在会将飞行和呱呱叫的动作"委托"别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法;

具体做法:

先在Duck类中"加入两个实例变量",分别为"flyBehavior"与"quackBehavior",声明为接口类型(而不是具体类实现类型),每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型(例如:FlyWithWings,Squeak等);我们也必须将Duck类与其所有子类中的fly()与quack()删除,因为这些行为已经被搬到FlyBehavior与QuackBehavior类中了。



实现performQuack():

public class Duck {

QuackBehaviorquackBehavior;//每只鸭子都会引用实现QuackBehavior接口的对象
public void performQuack(){
quackBehavior.quack();//鸭子对象不亲自处理呱呱叫行为,而是委托给quackBehavior引用的对象
}
}

如何设定flyBehavior与quackBehavior的实例变量呢?先来看看MallardDuck类:

public class MallardDuckextends Duck {

public MallardDuck(){
//绿头鸭使用Quack类处理呱呱叫,所以当performQuack()被调用时,叫的职责被委托给Quack对象,而我们得到了真正的呱呱叫
quackBehavior =new Quack();
//使用FLyWithWings作为其FlyBehavior类型
flyBehavior =new FlyWithWings();
}
//因为MallardDuck继承Duck类,所以具有flyBehavior与quackBehavior实例变量
public void display(){
System.out.println("I`m a real Mallard duck");
}


所以,绿头鸭会真的"呱呱叫",而不是"吱吱叫",或"叫不出声"。当MallardDuck实例化时,它的构造器会把继承来的quackBehavior实例变量初始化成Quack类型的新实例(Quack是QuackBehavior的具体实现类)

同样的方式,也可以用在飞行行为上:MallardDuck的构造器将flyBehavior实例变量初始化成FlyWithWings类型的实例(FlyWithWings是FlyBehavior的具体实现类)

现在来看看升级后的Duck类

public abstract class Duck {

FlyBehavior flyBehavior;//为行为接口类型声明两个引用变量,所有鸭子子类(在同一个package中)都继承它们
QuackBehavior quackBehavior;//每只鸭子都会引用实现QuackBehavior接口的对象
public Duck(){}
public abstract void display();
public void performQuack(){
quackBehavior.quack();//鸭子对象不亲自处理呱呱叫行为,而是委托给quackBehavior引用的对象
}
public void performFly(){
flyBehavior.fly();//委托给行为类
}
public void swim(){
System.out.println("All ducks float,even decoys!");
}
}

再来看看与飞行相关的几个接口与行为类

/**

*所有飞行行为类必须实现的接口

*/

public interface FlyBehavior {

void fly();
}

public class FlyWithWingsimplements FlyBehavior {

//这是飞行行为的实现,给"真会"飞的鸭子用.....
@Override
public void fly() {
System.out.println("I`m flying!");
}
}

public class FlyNoWayimplements FlyBehavior {

//这是飞行行为的实现,给"不会"飞的鸭子用(包括橡皮鸭和木头鸭)
@Override
public void fly() {
System.out.println("I can`t fly");
}
}

再来看看与叫声相关的几个接口与行为类

public interface QuackBehavior {

void quack();
}

public class Quackimplements QuackBehavior {

@Override
public void quack() {
System.out.println("Quack");
}
}

public class MuteQuackimplements QuackBehavior {

@Override
public void quack() {
System.out.println("<< Silence >>");
}
}

public class Squeakimplements QuackBehavior {

@Override
public void quack() {
System.out.println("Squeak");
}
}

测试

public class MiniDuckSimulator {

public static void main(String[]args) {
Duckmallard =
new MallardDuck();
//这会调用MallardDuck继承来的performQuack()方法,进而委托给该对象的QuackBehavior对象处理
//也就是说,调用继承来的quackBehavior引用对象的quack()
mallard.performQuack();
//至于performFly(),也是一样的道理
mallard.performFly();
}
}

运行结果:

Quack

I`m flying!

动态设定行为

假设我们想在鸭子子类中通过"设定方法(setter method)"来设定鸭子的行为,而不是鸭子的构造器内实例化
在Duck类中,加入两个新方法:

public void setFlyBehavior(FlyBehaviorfb){

flyBehavior =fb;
}

public void setQuackBehavior(QuackBehaviorqb){

quackBehavior =qb;
}

制造一个新的鸭子类型:模型鸭(ModelDuck.java)

public class ModelDuckextends Duck{

public ModelDuck(){
flyBehavior =new FlyNoWay();//一开始,模型鸭子是不会飞行的
quackBehavior =new Quack();
}
public void display(){
System.out.println("I`m a model duck");
}
}

建立一个新的FlyBehavior类型

 /**

*利用火箭飞行的能力

*/

public class FlyRocketPoweredimplements FlyBehavior {

@Override
public void fly() {
System.out.println("I`m flying with a rocket!");
}
}

改变测试类,加上模型鸭,并使模型鸭具有火箭动力

public class MiniDuckSimulator {

public static void main(String[]args) {
Duckmallard =
new MallardDuck();
mallard.performQuack();
mallard.performFly();
Duckmodel =
new ModelDuck();
//第一次调用performFly()会被委托给flyBehavior对象(也就是FlyNoWay实例)
//该对象是在模型鸭构造器中设置的
model.performFly();
//这会调用继承来的setter方法,把火箭动力飞行的行为设定到模型鸭中。模型鸭可以飞啦!
model.setFlyBehavior(new FlyRocketPowered());
//如果成功了,就意味着模型鸭可以动态地改变它的飞行行为。
//如果把行为的实现绑死在鸭子类中,就无法做到这样了!
model.performFly();
}
}

效果:

Quack

I`m flying!

I can`t fly

I`m flying with a rocket!

封装行为的大局观
将整个程序重新设计后,鸭子继承Duck,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口;
不再把鸭子的行为说成是"一组行为",开始把行为想成是"一族算法"。算法代表鸭子能做的事情(不同的叫法和飞行法);

请注意类之间的"关系"。关系可以是IS-A(是一个),HAS-A(有一个),IMPLEMENTS(实现)
"有一个"可能比"是一个"更好;"有一个"关系相当有趣,每一鸭子都有一个FlyBehavior和一个QuackBehavior,好将飞行和呱呱叫委托给它们代为处理;
当你将两个类结合起来使用,如同本例,就是组合。这种做法和继承的不同在于,鸭子的行为不是继承来的,而是和适当的行为对象"组合"来的;

类继承图示

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 设计模式