策略模式(Strategy)

1. 模式的定义

假设现在要实现一个简化的报价管理,实现如下的功能:
1. 对于普通客户或者新客户报全价
2. 对于老客户报的价格,统一折扣5%
3. 对于大客户报的价格,统一折扣10%
对不同的人员报不同的价格

到底该如何实现,才能够让价格类中的计算报价的算法,能很容易地实现可维护,可扩展,又能动态地切换变化呢?

策略模式的定义:
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换,使得算法可独立于使用它的客户而变化

2. UML图

策略模式(Strategy)_第1张图片

Strategy:策略接口,用来约束一系列具体的策略算法,Context使用这个接口来调用具体的策略实现定义的算法

ConcreteStrategy:具体的策略实现,也就是具体的算法实现

Context:上下文,负责和具体的策略类交互,通常会持有一个真正的策略实现

代码:


/**
 * 策略,定义计算报价算法的接口
 */
public interface Strategy {
    /**
     * 计算应报的价格
     * @param goodsPrice 商品销售原价
     * @return 计算出来的,应该给客户报的价格
     */
    public double calcPrice(double goodsPrice);
}

/**
 * 具体算法实现,为新客户或者是普通客户计算应报的价格
 */
public class NormalCustomerStrategy implements Strategy{
    public double calcPrice(double goodsPrice) {
        System.out.println("对于新客户或者是普通客户,没有折扣");
        return goodsPrice;
    }
}

/**
 * 具体算法实现,为老客户计算应报的价格
 */
public class OldCustomerStrategy implements Strategy{
    public double calcPrice(double goodsPrice) {
        System.out.println("对于老客户,统一折扣5%");
        return goodsPrice*(1-0.05);
    }
}

/**
 * 具体算法实现,为大客户计算应报的价格
 */
public class LargeCustomerStrategy implements Strategy{
    public double calcPrice(double goodsPrice) {
        System.out.println("对于大客户,统一折扣10%");
        return goodsPrice*(1-0.1);
    }
}

/**
 * 价格管理,主要完成计算向客户所报价格的功能
 */
public class Price {
    /**
     * 持有一个具体的策略对象
     */
    private Strategy strategy = null;
    /**
     * 构造方法,传入一个具体的策略对象
     * @param aStrategy 具体的策略对象
     */
    public Price(Strategy aStrategy){
        this.strategy = aStrategy;
    }   
    /**
     * 报价,计算对客户的报价
     * @param goodsPrice 商品销售原价
     * @return 计算出来的,应该给客户报的价格
     */
    public double quote(double goodsPrice){
        return this.strategy.calcPrice(goodsPrice);
    }
}

public class Client {
    public static void main(String[] args) {
        //1:选择并创建需要使用的策略对象
        Strategy strategy = new LargeCustomerStrategy();
        //2:创建上下文
        Price ctx = new Price(strategy);

        //3:计算报价
        double quote = ctx.quote(1000);
        System.out.println("向客户报价:"+quote);
    }
}


3. 研磨设计模式

1)策略模式的功能:把具体的算法实现从具体业务处理中独立出来,把它们实现成为单独的算法类,从而形成一系列的算法,并让这些算法可以相互替换

2)Context和Strategy的关系
策略实现对象也可以从上下文获取所需要的数据,可以将上下文当作参数传递给策略实现对象。

/**
 * 支付工资的策略的接口,公司有多种支付工资的算法
 * 比如:现金、银行卡、现金加股票、现金加期权、美元支付等等
 */
public interface PaymentStrategy {
    /**
     * 公司给某人真正支付工资
     * @param ctx 支付工资的上下文,里面包含算法需要的数据
     */
    public void pay(PaymentContext ctx);
}

/**
 * 人民币现金支付
 */
public class RMBCash implements PaymentStrategy{

    public void pay(PaymentContext ctx) {
        System.out.println("现在给"+ctx.getUserName()+"人民币现金支付"+ctx.getMoney()+"元");
    }

}

/**
 * 美元现金支付
 */
public class DollarCash implements PaymentStrategy{

    public void pay(PaymentContext ctx) {
        System.out.println("现在给"+ctx.getUserName()+"美元现金支付"+ctx.getMoney()+"元");
    }
}


/**
 * 支付到银行卡
 */
public class Card implements PaymentStrategy{

    public void pay(PaymentContext ctx) {
        //这个新的算法自己知道要使用扩展的支付上下文,所以强制造型一下
        PaymentContext2 ctx2 = (PaymentContext2)ctx;
        System.out.println("现在给"+ctx2.getUserName()+"的"+ctx2.getAccount()+"帐号支付了"+ctx2.getMoney()+"元");
        //连接银行,进行转帐,就不去管了
    }
}

/**
 * 支付到银行卡
 */
public class Card2 implements PaymentStrategy{
    /**
     * 帐号信息
     */
    private String account = "";
    /**
     * 构造方法,传入帐号信息
     * @param account 帐号信息
     */
    public Card2(String account){
        this.account = account;
    }
    public void pay(PaymentContext ctx) {
        System.out.println("现在给"+ctx.getUserName()+"的"+this.account+"帐号支付了"+ctx.getMoney()+"元");
        //连接银行,进行转帐,就不去管了
    }
}


/**
 * 支付工资的上下文,每个人的工资不同,支付方式也不同
 */
public class PaymentContext {
    /**
     * 应被支付工资的人员,简单点,用姓名来代替
     */
    private String userName = null;
    /**
     * 应被支付的工资的金额
     */
    private double money = 0.0;
    /**
     * 支付工资的方式策略的接口
     */
    private PaymentStrategy strategy = null;
    /**
     * 构造方法,传入被支付工资的人员,应支付的金额和具体的支付策略
     * @param userName 被支付工资的人员
     * @param money 应支付的金额
     * @param strategy 具体的支付策略
     */
    public PaymentContext(String userName,double money,PaymentStrategy strategy){
        this.userName = userName;
        this.money = money;
        this.strategy = strategy;
    }

    /**
     * 立即支付工资
     */
    public void payNow(){
        //使用客户希望的支付策略来支付工资
        this.strategy.pay(this);
    }
    public String getUserName() {
        return userName;
    }

    public double getMoney() {
        return money;
    }
}

/**
 * 扩展的支付上下文对象
 */
public class PaymentContext2 extends PaymentContext {
    /**
     * 银行帐号
     */
    private String account = null;
    /**
     * 构造方法,传入被支付工资的人员,应支付的金额和具体的支付策略
     * @param userName 被支付工资的人员
     * @param money 应支付的金额
     * @param account 支付到的银行帐号
     * @param strategy 具体的支付策略
     */
    public PaymentContext2(String userName,double money,String account,PaymentStrategy strategy){
        super(userName,money,strategy);
        this.account = account;
    }
    public String getAccount() {
        return account;
    }
}

public class Client {
    public static void main(String[] args) {
        //创建相应的支付策略
        PaymentStrategy strategyRMB = new RMBCash();
        PaymentStrategy strategyDollar = new DollarCash();

        //准备小李的支付工资上下文
        PaymentContext ctx1 = new PaymentContext("小李",5000,strategyRMB);
        //向小李支付工资
        ctx1.payNow();

        //切换一个人,给petter支付工资
        PaymentContext ctx2 = new PaymentContext("Petter",8000,strategyDollar);
        ctx2.payNow();

        //测试新添加的支付方式
        PaymentStrategy strategyCard = new Card();
        PaymentContext ctx3 = new PaymentContext2("小王",9000,"010998877656",strategyCard);
        ctx3.payNow();

        //测试新添加的支付方式
        PaymentStrategy strategyCard2 = new Card2("010998877656");
        PaymentContext ctx4 = new PaymentContext("小张",9000,strategyCard2);
        ctx4.payNow();
    }
}

3) 容错恢复机制
比如说采用策略模式,把日志记录到数据库和把日志记录到文件当做两种记录日志的策略,然后再运行期间根据需要进行动态的切换

4)策略模式结合模板方法模式
发现一系列的算法的实现上存在公共功能,只是在某些局部步骤上有所不同。

/**
 * 日志记录策略的接口
 */
public interface LogStrategy {
    /**
     * 记录日志
     * @param msg 需记录的日志信息
     */
    public void log(String msg);
}

/**
 * 实现日志策略的抽象模板,实现给消息添加时间
 */
public abstract class LogStrategyTemplate implements LogStrategy{

    public final void log(String msg) {
        //第一步:给消息添加记录日志的时间
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        msg = df.format(new java.util.Date())+" 内容是:"+ msg;
        //第二步:真正执行日志记录
        doLog(msg);
    }
    /**
     * 真正执行日志记录,让子类去具体实现
     * @param msg 需记录的日志信息
     */
    protected abstract void doLog(String msg);
}

/**
 * 把日志记录到数据库
 */
public class DbLog extends LogStrategyTemplate{
    public void doLog(String msg) {     
        //制造错误
        if(msg!=null && msg.trim().length()>5){
            int a = 5/0;
        }
        System.out.println("现在把 '"+msg+"' 记录到数据库中");
    }
}


/**
 * 把日志记录到数据库
 */
public class FileLog extends LogStrategyTemplate{
    public void doLog(String msg) {
        System.out.println("现在把 '"+msg+"' 记录到文件中");
    }
}

/**
 * 日志记录的上下文
 */
public class LogContext {
    /**
     * 记录日志的方法,提供给客户端使用
     * @param msg 需记录的日志信息
     */
    public void log(String msg){
        //在上下文里面,自行实现对具体策略的选择
        //优先选用策略:记录到数据库
        LogStrategy strategy = new DbLog();
        try{
            strategy.log(msg);
        }catch(Exception err){
            //出错了,那就记录到文件中
            strategy = new FileLog();
            strategy.log(msg);
        }
    }   
}


public class Client {
    public static void main(String[] args) {
        LogContext log = new LogContext();
        log.log("记录日志");
        log.log("再次记录日志");
    }
}

5)策略模式的本质:分离算法,选择实现

你可能感兴趣的:(设计模式,设计模式之全解析)