标签:对象 tps 取数 original test 变形 ddb 构造 而在
概念
在完成某一功能时,有时需要根据不同环境采取不同的策略或行为。将这些不同的策略或行为(称为算法)一一封装起来,而不是使用if--else,从而在使用的时候,可以将这些算法任意替换。这就是策略模式。
使用场景
策略模式的结构
假如现在要对不同客户进行报价,则不同客户会有不同的报价,假设①普通客户或新客户报全价;②老客户统一折扣5%;③大客户统一折扣10%。
使用策略模式,首先创建策略接口,里面仅定义一个用于计算报价的抽象方法calcPrice()
:
public interface Strategy {
/**
* 计算应报的价格,即计算报价
* @param goodsPrice 商品销售原价
* @return 报价
*/
double calcPrice(double goodsPrice);
}
接下来,为三种具体策略分别创建实现类:
//新客户或普通客户
public class NormalCustomerStrategy implements Strategy {
@Override
public double calcPrice(double goodsPrice) {
return goodsPrice;
}
}
//老客户
public class OldCustomerStrategy implements Strategy {
@Override
public double calcPrice(double goodsPrice) {
return goodsPrice*(1-0.05);
}
}
//大客户
public class BigCustomerStrategy implements Strategy {
@Override
public double calcPrice(double goodsPrice) {
return goodsPrice*(1-0.1);
}
}
最后,创建上下文类,取名为Price:
public class Price {
//虽然是一个接口,但实际是持有具体的策略实现类对象
private Strategy strategy;
/**
* 构造方法,传入一个具体的策略对象
* @param strategy 具体的策略对象
*/
public Price(Strategy strategy){
this.strategy = strategy;
}
/**
* 计算报价
* @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 BigCustomerStrategy();
//2:创建上下文
Price price = new Price(strategy);
//3:计算报价
double quote = price.quote(1000);
System.out.println("应该向客户报价:" + quote);
}
}
在这个例子中,策略算法即报价的计算只需要一个参数:商品原价。这种情况是最简单的,很好处理。
假如不同的策略算法需要的参数是不确定的,又该怎么办?
上面提到的问题的解决方案有两种:①将不同策略算法所需要的参数放在上下文中,然后将上下文传给策略算法;②将不同策略算法所需要的特定的数据放在各自的实现类中。
实际场景:不同员工的工资支付方案不同,比如现金、银行卡、现金加股票、现金加期权、美元等。此时,现金支付不需要银行账号,而银行卡则需要银行账号。
现在,先假设工资支付只支持人民币支付和美元支付。按照策略模式,我们来创建相关的类。
首先,策略接口,注意这里支付方法的参数为上下文(为了之后的扩展):
public interface PaymentStrategy {
/**
* 支付
* @param context 支付上下文
*/
void pay(PaymentContext context);
}
对应的支付上下文类为PaymentContext:
public class PaymentContext {
private String username;
private double money;
private PaymentStrategy strategy;
public PaymentContext(String username, double money, PaymentStrategy strategy){
this.username = username;
this.money = money;
this.strategy = strategy;
}
//get方法,以便策略算法在计算时从上下文获取所需数据
public String getUsername() {
return username;
}
//get方法,以便策略算法在计算时从上下文获取所需数据
public double getMoney() {
return money;
}
/**
* 支付工资
*/
public void payNow(){
this.strategy.pay(this);
}
具体的两个实现类:人民币现金支付RMBCash
和美元现金DollarCash
支付:
//人民币现金支付
public class RMBCash implements PaymentStrategy {
@Override
public void pay(PaymentContext context) {
System.out.println("给" + context.getUsername() + "人民币现金支付:" + context.getMoney());
}
}
//美元现金支付
public class DollarCash implements PaymentStrategy {
@Override
public void pay(PaymentContext context) {
System.out.println("给" + context.getUsername() + "美元现金支付:" + context.getMoney());
}
}
至此,所有策略模式相关类都已创建完成。
客户端使用的时候:
public class Client {
public static void main(String[] args){
PaymentStrategy strategyRMB = new RMBCash();
PaymentStrategy strategyDollar = new DollarCash();
PaymentContext context1 = new PaymentContext("小王", 8000, strategyRMB);
context1.payNow();
PaymentContext context2 = new PaymentContext("Peter", 6000, strategyDollar);
context2.payNow();
}
}
现在,进行扩展:要支持银行卡支付,此时需要将银行卡号这个信息传入具体的策略实现类。根据上面提到的两种解决方案,这个银行卡信息可以放在上下文中,也可以直接放在策略实现类中。现在先看第一种:将银行卡号信息放在上下文中。
此时,创建一个新的上下文类PaymentContext2
,使其继承原来的上下文PaymentContext
,并增加一个成员变量account
:
public class PaymentContext2 extends PaymentContext{
//银行卡号
private String account;
public PaymentContext2(String username, double money, String account, PaymentStrategy strategy) {
super(username, money, strategy);
this.account = account;
}
//get方法,以便策略算法在计算时从上下文获取所需数据
public String getAccount() {
return account;
}
}
然后,增加一个PaymentStrategy
实现类
public class Card implements PaymentStrategy {
@Override
public void pay(PaymentContext context) {
//进行强转
PaymentContext2 context2 = (PaymentContext2)context;
//转账
System.out.println("给" + context2.getUsername() + "的银行卡号" + context2.getAccount() + "支付:" + context2.getMoney());
}
}
好了,可以调用了:
public class Client {
public static void main(String[] args){
PaymentStrategy strategyRMB = new RMBCash();
PaymentStrategy strategyDollar = new DollarCash();
PaymentContext context1 = new PaymentContext("小王", 8000, strategyRMB);
context1.payNow();
PaymentContext context2 = new PaymentContext("Peter", 6000, strategyDollar);
context2.payNow();
//新增银行卡支付
PaymentStrategy strategyCard = new Card();
PaymentContext2 context3 = new PaymentContext2("小李", 5000, "6223987999987788", strategyCard);
context3.payNow();
}
}
现在,看另外一个解决方法:将银行号账号信息放在具体的策略实现类中。此时,就不必扩展上下文,只需要策略实现类即可,只是这个策略实现类除了支付算法,还要支付算法用到的特定数据,也就是说该实现类在进行策略计算时,所需数据不能完全从上下文中获取,因为自身持有所需要的必要信息。该实现类具体如下:
public class Card2 implements PaymentStrategy {
private String account;
public Card2(String account){
this.account = account;
}
@Override
public void pay(PaymentContext context) {
System.out.println("给" + context.getUsername() + "的银行卡号" + this.account + "支付:" + context.getMoney());
}
}
好了,调用时:
public class Client {
public static void main(String[] args){
PaymentStrategy strategyRMB = new RMBCash();
PaymentStrategy strategyDollar = new DollarCash();
PaymentContext context1 = new PaymentContext("小王", 8000, strategyRMB);
context1.payNow();
PaymentContext context2 = new PaymentContext("Peter", 6000, strategyDollar);
context2.payNow();
//新增银行卡支付
PaymentStrategy strategyCard2 = new Card2("6223987999987788");
PaymentContext context4 = new PaymentContext("小张", 7000, strategyCard2);
context4.payNow();
}
}
这两种方式的比较:
扩展上下文的方式:所有的策略的实现风格统一,策略所需要的数据都统一从上下文中获取;在上下文中添加的新数据,别的算法也可以用的上,可以视为公共的数据;但缺点是,如果这些数据都只有一个特定的策略算法适用,那么这些数据就有些浪费;另外每次添加新的算法都去扩展上下文,容易形成复杂的上下文对象层次。
在具体的策略算法上添加自己需要的数据:这样实现比较简单。但缺点是,跟其他策略实现的风格不一致,其他策略实现都是通过上下文统一获取数据,但这个策略一部分数据来自上下文,一部分数据来自自己,有些不统一;另外,外部使用这些策略方法的时候也不一样了,难以以一个统一的方法来动态切换策略算法。
两种实现各有优劣,要具体情况具体分析。
上面的两个场景中,均是由客户端来选择具体的算法,现在这个例子是根据上下文来选择具体的策略算法。
实际场景:在一个系统中,把日志记录到数据库和日志文件中(可以看做两种策略),然后再运行期间根据需要进行动态地切换。
首先,定义策略接口:
public interface LogStrategy {
void log(String message);
}
然后,两个实现类:
//数据库记录
public class DbLog implements LogStrategy {
@Override
public void log(String message) {
System.out.println("将" + message + "记录到数据库");
}
}
//文件记录
public class FileLog implements LogStrategy {
@Override
public void log(String message) {
System.out.println("将" + message + "记录到文件");
}
}
最后,上下文:
public class LogContext {
/**
* 记录日志的方法,提供给客户端使用
*
* 在上下文中,自行实现对具体策略的选择
*
* @param message
*/
public void log(String message){
//优先选用策略:记录到数据库
LogStrategy strategy = new DbLog();
try {
strategy.log(message);
}catch (Exception e){
//数据库记录日志出错,就记录到为日志文件中
strategy = new FileLog();
strategy.log(message);
}
}
}
好了,客户端可以调用了,相比之前的代码比较简单,因为策略选择的代码在策略上下文中实现,而不是让客户端来选择:
public class Client {
public static void main(String[] args){
LogContext context = new LogContext();
context.log("记录日志");
}
}
如果没有上下文,策略模式就回到了最基本的接口和实现了。上下文的意义在于,客户端不必直接与具体的策略交互,上下文还可以提供一些公共功能或是存储相关状态,减小客户端使用的难度。
策略模式的本质在于:分离算法,选择实现。
策略模式很好地体现了设计原则中的开闭原则:通过把一系列可变的算法进行封装,并定义出合理的结构,使得在系统出现新的算法的时候,很容易把新的算法加入到已有的系统中,而已有的实现不需要做任何修改。
内容绝大部分摘抄自《研磨设计模式》
标签:对象 tps 取数 original test 变形 ddb 构造 而在
原文地址:https://www.cnblogs.com/xiaoshen666/p/11258562.html