在引入策略模式之前。先用一个小项目解释为什么要有策略模式。这是一个模拟鸭子的小游戏。最开始需求为,所有的鸭子都会叫以及都会游泳且叫声游泳姿势都一样。因此我们用原始OO的思维去编写代码时很容易做到
public abstract class Duck{
public void yuck(){
System.out.println("嘎嘎叫");
} public void swim(){
Systrm.out.println("i am swim");
} public abstract void display();
}
也就是封装一个超类。然后设置两个子类通用的方法,叫和游泳。此时再编写程序需要的各种不同的鸭子子类。他们都能够叫和游泳。此时编写一个测试类。
public class Test{ public static void main(String[] args){
Duck gDuck = new GreenDuck();
Duck rDuck = new RedDuck(); gDuck.display();
gDuck.yuck();
gDuck.swim(); rDuck.display();
rDuck.yuck();
rDuck.swim(); }
}
public class GreenHeadDuck extends Duck{ @override
public void display(){
System.out.println("绿头鸭");
}
}
public class RedHeadDuck extends Duck{ @override
public void display(){
System.out.println("红头鸭");
}
}
测试类的结果为:绿头鸭 嘎嘎叫 i am swim 红头鸭 嘎嘎叫 i am swim。这个完全没有问题。好的,现在游戏上线,市场火热,新需求来了。鸭子要会飞。那我们继续用OO的思维去想,
那么可以给超类Duck中设置一个通用的fly方法。让所有的鸭子都会飞。让我们把Duck的代码改一下。改成下面的
public abstract class Duck{
public void yuck(){
System.out.println("嘎嘎叫");
} public void swim(){
Systrm.out.println("i am swim");
} public abstract void display(); public void fly(){
System.out.println("i am flying");
}
}
好的,现在的确实现了鸭子会飞的功能,但是这也引起了一些问题。因为事实上不是所有的鸭子都会飞。你在超类中定义了方法。影响的是全部的子类。容易导致溢出的问题,也就是。有些子类不需要。但是你给它强制加了。好,那如果现在有些子类不会飞。我们继续用OO的思维去解决。那么可以在不会飞的子类中用重写去覆盖掉父类给他的方法。我们加入绿头鸭不会飞,我们修改以下绿头鸭的代码如下
public class GreenHeadDuck extends Duck{ @override
public void display(){
System.out.println("绿头鸭");
} @override
public void fly(){
System.out.println("i can't fly")
}
}
好,可以通过在子类中覆盖来实现有些鸭子会费飞有些不会飞这个需求。但是在我们这个例子中鸭子种类少,这么玩没有关系。但如果由2000种鸭子呢。难道你要一个一个的去覆盖子类中的方法。
很明显就能看出单纯的面向对象,也就是OO的思想在设计这个需求的时候并不完善。那也有些人说,那我不在超类Duck中定义fly的方法。我在子类中去定义,谁会飞我就给谁定义。这其实和上面一样。如果子类基数大。那么你写的重复代码也会很多。那么OO中的代码复用就完全没有体现出来了。
因此,我们分析以下上面OO设计的问题所在。为什么会说这个设计不好。原因是在于它的行为和客户类没有分离。也就是“干什么” 和 “怎么干”没有分离开来。例如上面的例子。客户类中的fly是干什么,但是怎么干,fly里面怎么定义代码,他们没有分离。全部堆在客户类中,耦合度太高。导致,你的“怎么干“放在客户类中定义,无论你放在超类还是子类。都容易出问题,因此就导出了策略模式这个概念
策略模式是干嘛的,是去耦合的。它所做的就是把”干什么“和”怎么干“分离开来。把”怎么干“封装到一个策略接口或者抽象类中。然后编写一系列的怎么干算法封装起来。客户类中只需要调用这个算法就ok了
策略模式的两种标准点的定义:
图视:
环境对象(Context):该类中实现了对抽象策略中定义的接口或者抽象类的引用。
抽象策略对象(Strategy):它可由接口或抽象类来实现。
具体策略对象(ConcreteStrategy):它封装了实现同不功能的不同算法。
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换,策略模式让算法独立于使用它的客户而独立变化。
使这些算法在客户端调用它们的时候能够互不影响地变化。
策略模式:分别封装行为接口,实现算法族,超类中放行为接口对象。在子类中的构造方法中去实例化行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能族,让行为算法的变化独立于算法的使用者。
使用策略模式的步骤:
1.分析变化的行为,拿出来进行封装成一个策略接口或者抽象类。然后定义该行为的多种实现算法。形成算法族
2.在客户超类中放策略接口。
3.在子类的构造方法中去具体实例化具体的策略算法
下面让我们看看用策略模式修改模拟鸭子的这个项目。
1.封装变化的部分。当前鸭子的变化行为为fly。有些会飞,有些不会飞。有些会飞的好,有些飞的不好。因此封装fly这个行为为一个策略接口。FlyObject
public interface FlyObject{
void fly();
}
接下来,编写项目中实现fly这个行为的多种算法。例如不会飞 nofly,飞的好,good fly,飞得不好 badfly。这三个行为族
public class NoFly implements FlyObject{ @override
public void fly(){
System.out.println("不会飞")
}
}
public class GoodFly implements FlyObject{ @override
public void fly(){
System.out.println("Good fly");
}
}
public class BadFly implements FlyObject{ @override
public void fly(){
System.out.println("bad fly");
}
} 之后在客户类的超类也就是Duck中放置策略接口,也就是FlyObject。
package com.lyh.strategy.duck; import com.lyh.strategy.flybehavior.FlyBehavior;
import com.lyh.strategy.yuckbehavior.YuckBehavior; public abstract class Duck { FlyBehavior flybehavior;
YuckBehavior yuckbehavior;
public Duck() { }; public void fly() {
flybehavior.fly();
} public void yuck() {
yuckbehavior.yuck();
} public abstract void display(); public void setFlyBehavior(FlyBehavior fb) {
this.flybehavior = fb;
} public void setYuckBehavior(YuckBehavior yb) {
this.yuckbehavior = yb;
} //这个方法子类通用,不会变化
public void swim() {
System.out.println("i am swim");
}
}
在客户类的子类中去实例化具体的策略接口算法。
package com.lyh.strategy.duck; import com.lyh.strategy.flybehavior.GoodFlyBehavior;
import com.lyh.strategy.yuckbehavior.GaGaYuckBehavior; public class GreenHeadDuck extends Duck { public GreenHeadDuck() {
flybehavior = new GoodFlyBehavior();
yuckbehavior = new GaGaYuckBehavior();
}
@Override
public void display() {
System.out.println("绿头鸭");
} }
package com.lyh.strategy.duck; import com.lyh.strategy.flybehavior.NoFlyBehavior;
import com.lyh.strategy.yuckbehavior.NoYuckBehavior; public class RedHeadDuck extends Duck { public RedHeadDuck() {
flybehavior = new NoFlyBehavior();
yuckbehavior = new NoYuckBehavior();
}
@Override
public void display() {
System.out.println("红头鸭");
} }
因此,通过这种方式,能够实现客户类中的行为和客户类相分离。把变化的行为独立出来。抽象成接口。这样如果有新的行为需求,可以直接在行为族中加入。如果有新的客户类对象。也可以让他们
具体选择的去实现行为族。达到降低耦合度的目的。 但是,策略模式也有缺点。也就是客户类必须了解策略行为族中的每一个算法的实现以及原理。只有全部知晓。客户类才能去区别调用他们而不会出错。这时策略模式必须要做到的一点。
我们什么时候使用策略模式:这个没有具体的需求。一般而言。如果你定义的抽象父类中的一些方法是可变的。子类中不一定需要这些方法的时候。建议用策略模式将变化的行为封装起来。这其实也是一种方法的封装。你将行为封装,那么其他的类也可以去使用。