一、策略模式概述
策略模式(又叫政策Policy模式),属于对象行为模式下的:Strategy类提供了可插入式(Pluggable)算法的实现方案。
策略模式的定义-意图:定义一系列算法,将每一个算法封装起来,并让它们互相替换。策略模式让算法可以独立于使用它的客户变化。
模式策略的优缺点:
- 优点:
- 1.提供了对开闭(开时针对扩展功能是开放的,闭对修改功能是关闭的)原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为;
- 2.提供了管理相关的算法族的办法;
- 3.提供了一种可以替换继承关系的办法;
- 4.可以避免多重条件选择语句;
- 5.提供了一种算法的复用机制,不同环境类可以方便地复用策略类。(单一职责)
- 缺点:
- 1.客户端必须知道所有的策略类,并自行决定使用哪一个策略类;
- 2.将造成系统产生很多具体策略类;
- 3.无法同时在客户端使用多个策略类。
- 适用环境:
- 1.一种系统需要动态地在几种算法中选择一种;
- 2.避免使用难以维护的多重条件选择语句;
- 3.不希望客户端知道复杂的、与算法相关的数据结构,提高算法的保密性与安全。
二、代码实现
策略模式的结构,包含3个角色:
- 1.环境(Context)角色:持有一个Strategy类的引用;
- 2.抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需要的接口;
- 3.具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
2.1 售票策略
2.1.1 抽象策略角色(接口实现,接口名为Discount)
package Strategy.Mticket;
//抽象策略,打折
public interface Discount {
public double calculate(double price);
}
2.1.2 具体策略角色:儿童票(ChildrenDiscount)、学生票(StudentDiscount)、VIP票(VIPDiscount)
package Strategy.Mticket;
//具体策略,儿童票
public class ChildrenDiscount implements Discount {
//代码的可维护性使用全局变量
private final double DISCOUNT = 10 ;
@Override
public double calculate(double price) {
// TODO 自动生成的方法存根
if(price>=20) {
System.out.println("儿童票:");
return price - DISCOUNT;
}
else {
return price;
}
}
}
package Strategy.Mticket;
//具体策略,学生票
public class StudentDiscount implements Discount {
private final double DISCOUNT=0.8;
@Override
public double calculate(double price) {
// TODO 自动生成的方法存根
System.out.println("学生票:");
return price * DISCOUNT;
}
}
package Strategy.Mticket;
//具体策略,VIP票
public class VIPDiscount implements Discount {
private final double DISCOUNT=0.5;
@Override
public double calculate(double price) {
// TODO 自动生成的方法存根
System.out.println("VIP票:");
System.out.println("增加积分:");
return price * DISCOUNT;
}
}
2.1.3 环境角色:电影票(MovieTicket)
package Strategy.Mticket;
//环境类,电影票
public class MovieTicket {
private double price;
private Discount discount; //对抽象折扣类的引用
/*public double getPrice() {
//调用折扣类的折扣价计算方法
//rerurn折后价
return discount.calculate(this.price);
}*/
public double getPrice() {
return price;
}
public double getDiscountPrice() {
return discount.calculate(this.price);
}
public void setPrice(double price) {
this.price = price;
}
public Discount getDiscount() {
return discount;
}
//注入一个折扣类
public void setDiscount(Discount discount) {
this.discount = discount;
}
}
2.1.4 main方法调用实现策略模式
package Strategy.Mticket;
public class Client2 {
public static void main(String[] args) {
// TODO 自动生成的方法存根
MovieTicket mt =new MovieTicket();
mt.setPrice(60.0);
//mt.setDiscount(new VIPDiscount());
//Discount ds =new StudentDiscount();
Discount ds =new VIPDiscount();
mt.setDiscount(ds);
System.out.println("折后价为:"+mt.getDiscountPrice());
}
}
2.1.5 实例化工具代码(XMLUtil)
如果想利用配置文件来实例化对象,可以减轻对代码的更改,直接对配置文件修改就可以。具体代码如下:
package Strategy.Mticket;
import java.io.*;
import javax.xml.parsers.*;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
//实例化对象,配置文件就是想要s实例的对象
public class XMLUtil {
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
public static Object getBean() {
try {
//创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
//file里就是配置文件
doc =builder.parse(new File("src//Strategy//Mticket//config2.xml"));
//获取包含类名的文本节点
NodeList nl =doc.getElementsByTagName("className");
//item这是一个数组,就是调用xml文件中的className内容
Node classNode=nl.item(0).getFirstChild();
//cName为类名,getNodeValue为字符串类型方法
String cName = classNode.getNodeValue();
//通过类名生成实例对象并将其返回
//forName为java的反射技术
Class c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
}catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
2.1.6 XML配置文件(config.xml)
<?xml version="1.0" encoding="UTF-8"?>
<config>
<className>Strategy.Mticket.VIPDiscount</className>
</config>
2.1.7 利用配置文件的main方法实现
package Strategy.Mticket;
public class Client {
public static void main(String[] args) {
// TODO 自动生成的方法存根
MovieTicket mt = new MovieTicket();
double originalPrice = 60.0;
double currentPrice;
mt.setPrice(originalPrice);
System.out.println("原始价格为:"+originalPrice);
System.out.println("-----------------------------");
Discount discount;
//读取配置文件并反射生成具体折扣对象
//强转
discount =(Discount)XMLUtil.getBean();
mt.setDiscount(discount);//注入折扣对象
currentPrice = mt.getDiscountPrice();
System.out.println("折后价为:"+currentPrice);
}
}
2.1.8 UML图
2.2 鸭子行为策略
进阶版策略模式
甲方:我需要一堆鸭子,红色的,绿色的,黑色的,还要会飞!还要会叫!
乙方:明白,N种鸭子就像电影票一样,只要继承了我的Duck类然后重写,就搞定一切!
甲方:哦吼!为什么橡皮鸭子会在天上飞?
乙方:无脑继承类是不对的!!!
解决方案:只用封装,继承好像不行,好像还有一个多态(接口)?是不是可以把飞这个行为定义成接口,然后把这个接口, 放到鸭子基类里面去!
真正的策略模式核心即:封装行为,依赖接口,组合代替继承!
2.2.1 抽象策略角色(鸭子抽象基类Duck、飞行为接口FlyBehavior、叫行为接口QuackBehavior)
package Strategy.behavior;
//抽象策略,鸭子
public abstract class Duck {
/**
* 飞行行为是动态的,可能会变的,因此抽成多个接口的组合,而不是让Duck类继承
*/
//接口变量
FlyBehavior flybehavior;//面向接口编程
QuackBehavior quackBehavior;
public void createfly() {
flybehavior.fly();
}
/**
* 每个鸭子的叫声不同,抽象成接口
*/
public void createquack() {
quackBehavior.quack();
}
//为了测试方便
public void test() {
this.display();
this.createquack();
this.createfly();
this.swim();
}
public void swim() {
System.out.println("我在游泳");
}
public abstract void display();
}
package Strategy.behavior;
// 飞行为
public interface FlyBehavior {
public void fly();
}
package Strategy.behavior;
//叫行为
public interface QuackBehavior {
public void quack();
}
2.2.2 具体策略角色(用翅膀飞FlyWithWings、不会飞FlyNoWay、会叫Quack、不会叫MuteQuack、摩擦声Squeak)
package Strategy.behavior;
//会飞
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
// TODO 自动生成的方法存根
System.out.println("我在飞,用翅膀飞");
}
}
package Strategy.behavior;
public class FlyNoWay implements FlyBehavior {
//不会飞
@Override
public void fly() {
// TODO 自动生成的方法存根
}
}
package Strategy.behavior;
//会叫
public class Quack implements QuackBehavior {
@Override
public void quack() {
// TODO 自动生成的方法存根
System.out.println("我在呱呱叫");
}
}
package Strategy.behavior;
//不会叫
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
// TODO 自动生成的方法存根
}
}
package Strategy.behavior;
//摩擦声
public class Squeak implements QuackBehavior {
@Override
public void quack() {
// TODO 自动生成的方法存根
System.out.println("我不会呱呱叫,但是我能发出橡皮与空气的摩擦声");
}
}
2.2.3 环境角色(诱饵鸭DecoyDuck、绿头会飞鸭MallardDuck、红头会飞鸭RedHeadDuck、橡皮鸭RubberDuck)
package Strategy.behavior;
//诱饵鸭
public class DecoyDuck extends Duck{
public DecoyDuck() {
quackBehavior =new MuteQuack();
flybehavior =new FlyNoWay();
}
@Override
public void display() {
// TODO 自动生成的方法存根s
System.out.println("我是一只诱饵鸭");
}
}
package Strategy.behavior;
//绿头会飞鸭
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior=new Quack();
flybehavior=new FlyWithWings();
}
@Override
public void display() {
// TODO 自动生成的方法存根
System.out.println("我是一只绿头,灰色羽毛的鸭子");
}
}
package Strategy.behavior;
//红头会飞鸭
public class RedHeadDuck extends Duck {
public RedHeadDuck() {
quackBehavior=new Quack();
flybehavior=new FlyWithWings();
}
@Override
public void display() {
// TODO 自动生成的方法存根
System.out.println("我是一只红头,灰色羽毛的鸭子");
}
}
package Strategy.behavior;
//橡皮鸭
public class RubberDuck extends Duck {
public RubberDuck(){
quackBehavior=new Squeak();
flybehavior =new FlyNoWay();
}
@Override
public void display() {
// TODO 自动生成的方法存根
System.out.println("我是一只黄色的橡皮鸭");
}
}
2.2.4 main方法调用实现策略模式
package Strategy.behavior;
public class Test {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Duck ducks[]=new Duck[4];
ducks[0]=new MallardDuck();
ducks[1]=new RedHeadDuck();
ducks[2]=new RubberDuck();
ducks[3]=new DecoyDuck();
ducks[0].test();
ducks[1].test();
ducks[2].test();
ducks[3].test();
}
}