java 设计模式:策略模式
概念:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换,策略模式让算法独立于使用它的客户而独立变化。
策略模式使这些算法在客户端调用它们的时候能够互不影响地变化。
使用场景:
一个类定义了多种行为,并且这个行为在这个类的方法中以多个条件语句形式出现,那么可以使用策略模式避免在类中使用大量的条件语句。
UML:
代码展示:
为了更清晰的展示出策略模式的优点,我在此写一套不用策略模式实现的代码。如下:
/**
* 有如下几个超市价格规则 1:五月一日 价格统统8折 2:十月一日 价格统统7折 3:十一月一日 价格统统半价
* Created by on 2019/4/30.
*/
public class Price {
public double jisuanPrice(double price,String s){
double p = price;
System.out.println(s);
switch (s){
case "五月一日":
p = price*0.8;
break;
case "十月一日":
p = price*0.7;
break;
case "十一月一日":
p = price*0.5;
break;
}
return p;
}
}
//运行
Price p = new Price();
System.out.println(p.jisuanPrice(10),"五月一日");
你会发现,也很清晰表示了判断不同的日期来计算不同的价格,不同日期用switch来判断,但是呢,如果日期很多,而且规则又有了对特定商品的价格规则,那么这个类的负担是否有些复杂了呢?这个类不是单一职责。还根据swith来计算规则。
于是我写下了如下的策略方法:
/**
* 半价计算策略
*/
public class Banjia implements IPrice {
@Override
public double jisuanPrice(double price) {
return price*0.5;
}
}
/**
* 8折计算策略
*/
public class BaZhe implements IPrice {
@Override
public double jisuanPrice(double price) {
return price*0.8;
}
}
/**
* 正常计算策略
*/
public class NorPrice implements IPrice {
@Override
public double jisuanPrice(double price) {
return price;
}
}
/**
* 7折计算
*/
public class Qizhe implements IPrice {
@Override
public double jisuanPrice(double price) {
return price*0.7;
}
}
//作为价格管理器一定要持有IPrice的引用
public class Price{
private IPrice iPrice;
public void setiPrice(IPrice iPrice) {
this.iPrice = iPrice;
}
public double jisuanPrice(double price) {
return iPrice.jisuanPrice(price);
}
}
//运行
//1.创建具体测策略实现
IPrice iprice = new BaZhe();
//2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
Price p = new Price();
p.setiPrice(iprice);
//3.调用上下文对象的方法来完成对具体策略实现的回调
System.out.println(p.jisuanPrice(10));
这个能够明显是对价格做了分类,假如说十月一日要进行半价打折,那你是不是很容易就改变了策略呢?不需要动其他代码。
策略模式优点
1、上下文Context 和具体策略器(oncreteStrategy)是松耦合关系。
2、满足开-闭原则。增加新的具体策略不需要修改context。
开闭原则:
- 对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
- 对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。
策略和上下文的关系:
- 在策略模式中,一般情况下都是上下文持有策略的引用,以进行对具体策略的调用。但具体的策略对象也可以从上下文中获取所需数据,可以将上下文当做参数传入到具体策略中,具体策略通过回调上下文中的方法来获取其所需要的数据。
如下例子来自 https://www.cnblogs.com/lewis0077/p/5133812.html 非常好的一篇文章
**下面我们演示这种情况:**
在跨国公司中,一般都会在各个国家和地区设置分支机构,聘用当地人为员工,这样就有这样一个需要:每月发工资的时候,中国国籍的员工要发人民币,美国国籍的员工要发美元,英国国籍的要发英镑。
public interface PayStrategy {
//在支付策略接口的支付方法中含有支付上下文作为参数,以便在具体的支付策略中回调上下文中的方法获取数据
public void pay(PayContext ctx);
}
//人民币支付策略
public class RMBPay implements PayStrategy {
@Override
public void pay(PayContext ctx) {
System.out.println("现在给:"+ctx.getUsername()+" 人民币支付 "+ctx.getMoney()+"元!");
}
}
//美金支付策略
public class DollarPay implements PayStrategy {
@Override
public void pay(PayContext ctx) {
System.out.println("现在给:"+ctx.getUsername()+" 美金支付 "+ctx.getMoney()+"dollar !");
}
}
//支付上下文,含有多个算法的公有数据
public class PayContext {
//员工姓名
private String username;
//员工的工资
private double money;
//支付策略
private PayStrategy payStrategy;
public void pay(){
//调用具体的支付策略来进行支付
payStrategy.pay(this);
}
public PayContext(String username, double money, PayStrategy payStrategy) {
this.username = username;
this.money = money;
this.payStrategy = payStrategy;
}
public String getUsername() {
return username;
}
public double getMoney() {
return money;
}
}
//外部客户端
public class Client {
public static void main(String[] args) {
//创建具体的支付策略
PayStrategy rmbStrategy = new RMBPay();
PayStrategy dollarStrategy = new DollarPay();
//准备小王的支付上下文
PayContext ctx = new PayContext("小王",30000,rmbStrategy);
//向小王支付工资
ctx.pay();
//准备Jack的支付上下文
ctx = new PayContext("jack",10000,dollarStrategy);
//向Jack支付工资
ctx.pay();
}
}
控制台输出:
现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
那现在我们要新增一个银行账户的支付策略,该怎么办呢?
显然我们应该新增一个支付找银行账户的策略实现,由于需要从上下文中获取数据,为了不修改已有的上下文,我们可以通过继承已有的上下文来扩展一个新的带有银行账户的上下文,然后再客户端中使用新的策略实现和带有银行账户的上下文,这样之前已有的实现完全不需要改动,遵守了开闭原则。
//银行账户支付
public class AccountPay implements PayStrategy {
@Override
public void pay(PayContext ctx) {
PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx;
System.out.println("现在给:"+ctxAccount.getUsername()+"的账户:"+ctxAccount.getAccount()+" 支付工资:"+ctxAccount.getMoney()+" 元!");
}
}
//带银行账户的支付上下文
public class PayContextWithAccount extends PayContext {
//银行账户
private String account;
public PayContextWithAccount(String username, double money, PayStrategy payStrategy,String account) {
super(username, money, payStrategy);
this.account = account;
}
public String getAccount() {
return account;
}
}
//外部客户端
public class Client {
public static void main(String[] args) {
//创建具体的支付策略
PayStrategy rmbStrategy = new RMBPay();
PayStrategy dollarStrategy = new DollarPay();
//准备小王的支付上下文
PayContext ctx = new PayContext("小王",30000,rmbStrategy);
//向小王支付工资
ctx.pay();
//准备Jack的支付上下文
ctx = new PayContext("jack",10000,dollarStrategy);
//向Jack支付工资
ctx.pay();
//创建支付到银行账户的支付策略
PayStrategy accountStrategy = new AccountPay();
//准备带有银行账户的上下文
ctx = new PayContextWithAccount("小张",40000,accountStrategy,"1234567890");
//向小张的账户支付
ctx.pay();
}
}
控制台输出:
现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
现在给:小张的账户:1234567890 支付工资:40000.0 元!
除了上面的方法,还有其他的实现方式吗?
当然有了,上面的实现方式是策略实现所需要的数据都是从上下文中获取,因此扩展了上下文;现在我们可以不扩展上下文,直接从策略实现内部来获取数据,看下面的实现:
//支付到银行账户的策略
public class AccountPay2 implements PayStrategy {
//银行账户
private String account;
public AccountPay2(String account) {
this.account = account;
}
@Override
public void pay(PayContext ctx) {
System.out.println("现在给:"+ctx.getUsername()+"的账户:"+getAccount()+" 支付工资:"+ctx.getMoney()+" 元!");
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
}
//外部客户端
public class Client {
public static void main(String[] args) {
//创建具体的支付策略
PayStrategy rmbStrategy = new RMBPay();
PayStrategy dollarStrategy = new DollarPay();
//准备小王的支付上下文
PayContext ctx = new PayContext("小王",30000,rmbStrategy);
//向小王支付工资
ctx.pay();
//准备Jack的支付上下文
ctx = new PayContext("jack",10000,dollarStrategy);
//向Jack支付工资
ctx.pay();
//创建支付到银行账户的支付策略
PayStrategy accountStrategy = new AccountPay2("1234567890");
//准备上下文
ctx = new PayContext("小张",40000,accountStrategy);
//向小张的账户支付
ctx.pay();
}
}
控制台输出:
现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
现在给:小张的账户:1234567890 支付工资:40000.0 元!
那我们来比较一下上面两种实现方式:
扩展上下文的实现:
优点:具体的策略实现风格很是统一,策略实现所需要的数据都是从上下文中获取的,在上下文中添加的数据,可以视为公共的数据,其他的策略实现也可以使用。
缺点:很明显如果某些数据只是特定的策略实现需要,大部分的策略实现不需要,那这些数据有“浪费”之嫌,另外如果每次添加算法数据都扩展上下文,很容易导致上下文的层级很是复杂。
在具体的策略实现上添加所需要的数据的实现:
优点:容易想到,实现简单
缺点:与其他的策略实现风格不一致,其他的策略实现所需数据都是来自上下文,而这个策略实现一部分数据来自于自身,一部分数据来自于上下文;外部在使用这个策略实现的时候也和其他的策略实现不一致了,难以以一个统一的方式动态的切换策略实现。