目录
经典的鸭子问题
我们先通过一个例子来逐步分析,为什么要使用“策略模式”解决鸭子问题。
现在有各种鸭子(比如野鸭、北京鸭、水鸭等,而鸭子有各种行为,例如叫和飞行),各种鸭子的行为是不一样的,如野鸭会飞,水鸭不会飞。怎么用程序实现各种鸭子的代码呢?
传统的方案是这样的。定义一个抽象类Duck,具体的鸭子继承这个Duck类,不同的鸭子类重写Duck类中的行为方法。
使用继承,子类重写父类有什么问题呢?
如果所有鸭子都继承了fly,代表所有鸭子都会飞,不会飞的鸭子,你这么写就很不合适。相当于new一个不会飞和叫的鸭子时,要先继承飞和叫接口,然后手动重写,让鸭子不会飞也不会叫~
策略模式的UML图
完全不用解释图中的各个元素代表什么意思,因为在下面的实战中,我们会把上面的实体名都替换成鸭子的相关名称~
策略模式解决鸭子问题
我们来“对号入座”。策略模式是把继承的强耦合给打散,祖宗类(接口)还是Duck。Duck下面有两个方法,飞和叫。但这两个方法中并没有写任何具体的实现!
我们把叫和飞两个方法单独抽离出来作为接口,我们让每种“叫声”单独作为一个类,每种“飞”的熟练度也单独作为一个类。这些具体的类继承于飞和叫两个接口,这两个接口又和Duck类是“组合/聚合关系"
接下来就有趣了,假如你要一个野鸭,它飞的贼6、叫的贼好该怎么搞呢?
飞接口
public interface FlyBehavior{
void fly();
}
飞接口的具体实现类:“飞的6类”、剩下的“不会飞类”、“飞的不咋地类”省略。
public class GoodFlyBechavior implements FlyBehavior {
@Override
public void fly(){
System.out.println("飞的贼6");
}
}
然后新建Duck类,Duck类中聚合了飞和叫两个接口,飞和叫各有一个方法,意思是只有你传入了fly或quack,才会使用你传入的fly或quack,否则默认不飞也不叫~
public abstract class Duck {
FlyBehavior flyBehavior; //飞接口
QuackBehavior quackBehavior; //叫接口
public Duck(){
}
public abstract void display(); //显示鸭子信息
public void quack(){
if(quackBechavior != null) {
quackBehavior.quack();
}
}
public void fly(){
if(flyBechavior != null) {
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
接下来我们新建野鸭类,北京鸭和玩具鸭相似这里省略
public class WildDuck extends Duck {
//构造器 我们new的是实现类
public WildDuck() {
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
System.out.println("这是野鸭");
}
}
最后我们来建立Client类,调用需要的鸭子
public class Client {
psvm(){
WildDuck wildDuck = new WildDuck();
wildDuck.fly();
}
}
通过client类我们可以发现,我们new了WildDuck后 ,实际上是用了封装在WildDuck类中的行为和方法。以后如果你需要让野鸭变得不会飞,找到WildDuck类,改成new NoFlyBehavior方法即可,其他完全不用动。我们成功做到了让变与不变相分离
这不算最妙的,最妙的是你可以在client类中动态的改变“野鸭的行为”。
经过下面的修改后,野鸭子不会叫了~
public class Client {
psvm(){
WildDuck wildDuck = new WildDuck();
wildDuck.setFlyBehavior(new NoFlyBehavior());
wildDuck.fly();
}
}
什么是策略模式
策略模式中定义了算法族(策略组),把变的部分和不变的部分拆分开,让算法的变化独立与使用算法的客户
策略模式体现了几个原则:针对接口编程、变与不变相分离、多用组合/聚合少用继承。
策略模式在Arrays工具类中的应用
还记得我们在java基础中学过的比较方法Comparator吗?
看看下面的代码,我们知道默认使用arrays.sort方法时是升序排列数组。那如何降序呢?jdk提供的方法是重写Comparator接口,默认是o1-o2,你要改成o2-o1
public class Testexample {
public static void main(String[] args) {
/*注意,要想改变默认的排列顺序,不能使用基本类型(int,double, char)
而要使用它们对应的类*/
Integer[] a = {9, 8, 7, 2, 3, 4, 1, 0, 6, 5};
//定义一个自定义类MyComparator的对象
Comparator cmp = new MyComparator();
Arrays.sort(a,cmp);
for(int arr:a) {
System.out.print(arr + " ");
}
}
}
//实现Comparator接口
class MyComparator implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
/*如果o1小于o2,我们就返回正值,如果o1大于o2我们就返回负值,
这样颠倒一下,就可以实现降序排序了,反之即可自定义升序排序了*/
return o2-o1;
}
}
为什么o1和o2顺序颠倒后,排列的升降也颠倒了呢?因为Comparator方法就使用了策略模式,算法的变化影响返回给客户的结果
用策略模式来替代if-else
这里拆分变与不变的部分我们就不做展示了,这里想说明的是,策略模式通过创建对象的方式代替了if-else
完整版欢迎阅读https://www.cnblogs.com/geekdc/p/11580219.html
使用策略模式的注意事项
1.策略模式的关键是分析项目中变化部分与不变部分
2.策略模式的核心是多用组合/聚合,少用继承
3需要注意没增加一个策略(算法),就要增加一个类。当策略过多时会导致类的数量庞大