赞
踩
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。
在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决这些问题。
策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
简述:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。
策略模式的主要优点如下。
其主要缺点如下。
模拟鸭子项目,具体要求如下:
1)有各种鸭子(比如 绿头鸭、红头鸭,鸭子有各种行为,比如叫、游泳)
2)显示鸭子的信息
传统方案解决鸭子问题的分析和代码实现
1)传统的设计方案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LReUNUvw-1608016555763)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\image-20201214135952353.png)]
2)代码实现
.java文件说明:
Duck.java:基类鸭子
GreenHeadDuck.java:绿头鸭
RedHeadDuck.java:红头鸭
SimulateDuck.java:模拟鸭子(main)
Duck.java
//抽象类:鸭子 public abstract class Duck { public Duck(){ //子类的构造函数中可以定义行为 } //在本抽象类中已经实现了 public void quack(){ System.out.println("~~嘎嘎叫~~"); } //由子类实现 public abstract void display(); //在本抽象类中自己已经实现了 public void swim(){ System.out.println("~~我会游泳~~"); } }
RedHeadDuck.java:
public class RedHeadDuck extends Duck{
@Override
public void display() {
System.out.println("我是独一无二的,我的头是红色的");
}}
GreenHeadDuck.java:
public class GreenHeadDuck extends Duck {
@Override
public void display() {
System.out.println("我和你们不一样,我的头是绿色的");
}
}
SimulateDuck.java:
GreenHeadDuck greenHeadDuck=new GreenHeadDuck();
RedHeadDuck redHeadDuck=new RedHeadDuck();
greenHeadDuck.display();
greenHeadDuck.quack();
greenHeadDuck.swim();
redHeadDuck.display();
redHeadDuck.quack();
redHeadDuck.swim();
我们已经实现了基本的项目需求了。模拟鸭子算是成功啦~!
但是项目添加了新的需求,添加会飞的鸭子(并不是所有的鸭子都会飞)
传统的方式实现的问题分析和解决方案
如果在基类写Fly()方法,其他鸭子,都继承了Duck类,所以fly让所有子类都会飞了,违背逻辑。
这个问题,是继承带来的问题,对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应
为了改进问题,我们可以通过覆盖fly方法来解决,
问题又来了,如果我们有一个玩具鸭子,这样需要玩具鸭子去覆盖Duck的所有实现的方法 ,覆盖工作量特别大
解决方法:策略模式(strategy pattern)思路:继承是实现共性,减少代码的重复。接口是实现特性。
策略模式解决鸭子问题的分析
1)设计方案
需要新的设计方式,应对项目的扩展性,降低复杂度:
a. 分析项目变化与不变部分,提取变化部分,抽象成接口+实现;(抽象是共性,接口是特性)
b. 鸭子那些功能是会根据新需求变化的?叫声、飞行
接口:
public interface FlyBehavior{
void fly();
}
public interface QuackBehavior{
void quack();
}
c. 好处:新增行为简单,行为类更好的复用,组合方便。既有继承带来的复用好处,没有挖坑
2)代码实现
.java文件说明:
Duck.java:基类鸭子
GreenHeadDuck.java:绿头鸭
RedHeadDuck.java:红头鸭
SimulateDuck.java:模拟鸭子(main)
FlyBehavior.java:(接口)特有的飞行行为
QuackBehavior.java:(接口)特有的叫喊行为
BadFlyBehavior.java:飞行行为的实现类
BadQuackBehavio.java:叫喊行为的实现类
Duck.java:
//抽象类:鸭子 public abstract class Duck { FlyBehavior flyBehavior; QuackBehavior quackBehavior; public Duck(){ //子类的构造函数中可以定义行为 } //在本抽象类中已经实现了 public void quack(){ //System.out.println("~~嘎嘎叫~~"); quackBehavior.quack(); } //由子类实现 public abstract void display(); //在本抽象类中自己已经实现了 public void swim(){ System.out.println("~~我会游泳~~"); } //实例化对象时可以动态的改变对象的行为(比继承灵活性强) public void SetFlyBehavior(FlyBehavior fb) { flyBehavior = fb; } //实例化对象时可以动态的改变对象的行为 public void SetQuackBehavior(QuackBehavior qb) { quackBehavior = qb; } public void fly(){ //System.out.println("飞"); flyBehavior.fly(); } }
GreenHeadDuck.java:
public class GreenHeadDuck extends Duck {
public GreenHeadDuck(){
//行为轴展示具体的行为
flyBehavior = new BadFlyBehavior();
}
@Override
public void display() {
System.out.println("我和你们不一样,我的头是绿色的");
}
//覆盖超类
// public void fly(){
// System.out.println("我不会飞");
// }
}
RedHeadDuck.java:
public class RedHeadDuck extends Duck {
public RedHeadDuck(){
quackBehavior = new BadQuackBehavior();
}
@Override
public void display() {
System.out.println("我是独一无二的,我的头是红色的");
}}
SimulateDuck.java:
/** * 主类:模拟鸭子 */ public class SimulateDuck { public static void main(String[] args) { //父类为Duck,屏蔽了超类的差别性 Duck greenHeadDuck = new GreenHeadDuck(); Duck redHeadDuck=new RedHeadDuck(); // GreenHeadDuck greenHeadDuck = new GreenHeadDuck(); // RedHeadDuck redHeadDuck = new RedHeadDuck(); greenHeadDuck.display(); greenHeadDuck.fly(); greenHeadDuck.SetQuackBehavior(new QuackBehavior() { @Override public void quack() { System.out.println("我会叫"); } }); greenHeadDuck.swim(); redHeadDuck.display(); redHeadDuck.quack(); redHeadDuck.swim(); redHeadDuck.SetFlyBehavior(new FlyBehavior() { @Override public void fly() { System.out.println("我会飞"); } }); } }
FlyBehavior.java:
public interface FlyBehavior {
void fly();
}
QuackBehavior.java:
public interface QuackBehavior {
void quack();
}
BadFlyBehavior.java:
public class BadFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("我不会飞");
}
}
BadQuackBehavio.java:
public class BadQuackBehavior implements QuackBehavior{
@Override
public void quack() {
System.out.println("我不会叫");
}
}
总结:运用设计模式中的策略模式,把变化的部分提取出来变为接口+实现。
Duck类中的SetQuackBehavoir()方法,灵活的让实例化对象灵活的改变对象的行为。比如,绿头鸭,使用了SetQuackBehavoir()方法,定制了自己的quck()方法。因为不是每只鸭都能叫的。叫的是当前鸭的特性。
策略模式体现了几个设计原则
封装变化:把变化的代码从不变的代码中分离出来(找出应用中可能需要变化之处,把它们独立出来,不要和哪些不需要变化的代码混在一起。)
针对接口编程而不是具体类(定义了策略接口)
多用组合/聚合,少用继承(客户通过组合方式使用策略)
以一个价格计算策略为背景
没有用策略模式
我们一般是下面的写法,直接写一个类,在类里面直接写策略算法(功能实现)
public class NoStrategy { /** * 传入客服等级类型获取相应的价格 * @param type 会员类型(等级) * @param price 响应的价格 * @return */ public double getPrice(String type, double price) { if ("普通客户小批量".equals(type)) { System.out.println("[未采用设计模式] 不打折,原价"); return price; } else if ("普通客户大批量".equals(type)) { System.out.println("[未采用设计模式] 打九折"); return price * 0.9; } else if ("老客户小批量".equals(type)) { System.out.println("[未采用设计模式] 打八折"); return price * 0.8; } else if ("老客户大批量".equals(type)) { System.out.println("[未采用设计模式] 打七折"); return price * 0.7; //拓展一种策略 // }else if("老客户特大批量".equals(type)){ // System.out.println("[未采用设计模式] 打六折"); // return price*0.6; } //乱传的也是当普通客户小批量(就是不打折) return price; } }
1.写一个策略接口Strategy
public interface Strategy {
/**
* 通过策略获取价格
* @param standardPrice
* @return
*/
double getPrice(double standardPrice);
}
2.面向接口,组合编程,少用继承(继承虽然可以复用代码,但是会造成耦合度增加,解决方式往往采用接口做类的属性),如下,这样所有实现Strategy 的各种策略实现类都"组合"到这个类里面了
public class Context { /** * 当前采用的算法对象 * 面向接口,组合编程,少用继承 * 简言之复杂类型(类,接口等)做属性 */ private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public double getReultPrice(double price){ return this.strategy.getPrice(price); } }
3:既然是策略模式接口Strategy都明确了要做的事情是根据会员情况,返回价格,但是没有真正的实现,那么总有类来实现赛
策略实现类1 VIP0Strategy
/**
* VIP0Strategy:普通会员策略
*/
public class VIP0Strategy implements Strategy {
/**
* 输入一个价格,经过VIP0Strategy策略计算价格
* @param standardPrice
* @return
*/
@Override
public double getPrice(double standardPrice) {
System.out.println("[策略模式]普通会员 原价:"+standardPrice);
return standardPrice;
}
}
策略实现类2 VIP1Strategy
/** * VIP1Strategy: 一级会员策略 */ public class VIP1Strategy implements Strategy { /** * 输入一个价格,经过VIP1Strategy策略计算价格 * @param standardPrice * @return */ @Override public double getPrice(double standardPrice) { System.out.println("[策略模式]一级会员 打九折:"+standardPrice * 0.9); return standardPrice * 0.9; } }
策略实现类3 VIP2Strategy
/**
* VIP2Strategy:二级会员策略
*/
public class VIP2Strategy implements Strategy {
/**
* 输入一个价格,经过VIP2Strategy策略计算价格
* @param standardPrice
* @return
*/
@Override
public double getPrice(double standardPrice) {
System.out.println("[策略模式]二级会员八折:"+standardPrice*0.8);
return standardPrice*0.8;
}
}
策略实现类4 VIP3Strategy(新增加的需求)
public class VIP3Strategy implements Strategy {
@Override
public double getPrice(double standardPrice) {
System.out.println("[策略模式]老客户特大批量:"+standardPrice*0.6);
return standardPrice*0.6;
}
}
4.客户端:
import com.cx.price.NoStrategy; import com.cx.price.VIP1Strategy; public class Client { public static void main(String[] args) { System.out.println("未使用模式-----------------------------------------"); NoStrategy noStrategy = new NoStrategy(); double price = noStrategy.getPrice("普通客户大批量", 1000); System.out.println(price); System.out.println("\n测试策略------------------------------------------"); Context context0 = new Context(new VIP1Strategy()); double resultPrice = context0.getReultPrice(1000); System.out.println(resultPrice); //怎么体现策略模式呢?比如现在需求是增加一种会员机制, '老客户特大批量' ,那么显然打折力度更大,我们设置为6折, // 分别在未使用策略模式和使用了策略模式的基础上拓展,看那个更加易于拓展,方便维护 //为了实现这么一个折扣计算功能,代码需要写4个if-else,如果需求再增多一个规则,代码还需重构if-else,这样在可维护性、可读性大大降低,而且修改容易出bug。 //如果运用策略模式,每个规则对应一个策略,根据符合的条件对应选择哪一种策略,这样整体代码逻辑清晰,而且不管新增或修改规则时,只需要新增或调整对应的规则策略,这样大大降低bug的风险,可维护性更高。 // //新增策略后未使用模式(会修该策略核心类) // NoStrategy noStrategy1 = new NoStrategy(); // double price1 = noStrategy1.getPrice("老客户特大批量", 1000); // System.out.println(price1); // // // //新增策略后使用模式(不会修改策略接口,只是添加一个实现) // Context context2 = new Context(new VIP3Strategy()); // double price2 = context2.getReultPrice(1000); // System.out.println(price2); } }
结论: 修改服务器端已经写好了的类是极其不好的维护形式,因为这个类NoStrategy可能在别的类中作为依赖或者叫做别的类引用了该类,在不明确的情况下,可能牵一发动全身,是不好的维护方式,使用了策略模式,我们只是添加了一个策略接口的实现,低侵入式,不会对已有代码造成影响,低耦合
说明:从上面这个图可以看出来客户context有成员变量strategy或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定
策略模式的主要角色如下:
1)策略模式的关键是 :分析项目中变化部分与不变部分
2)策略模式的核心思想是 :多用组合/聚合,少用继承;用行为类组合,而不是行为的继承。更有弹性。
3)体现了“开闭原则”(对修改关闭,对扩展开放)。客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if … else);
4)提供了可以替换继承关心的办法 :策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
5)需要注意的是 :每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。