目录

1 策略模式概述

2 策略模式的结构与实现

1. 模式的结构

2. 模式的实现

3 策略模式的应用实例

通用实现

报价管理的例子

策略和上下文的实例


1 策略模式概述

 策略这个词应该怎么理解,打个比方说,我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。

  再比如我们去逛商场,商场现在正在搞活动,有打折的、有满减的、有返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身只是一种策略,并且这些算法是随时都可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。

  策略模式是对算法的包装,把使用算法的责任和算法本身分隔开,委派给不同的对象管理。策略模式通常把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。

策略模式的主要优点如下。

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。


其主要缺点如下。

  1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  2. 策略模式造成很多的策略类。

2 策略模式的结构与实现

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。

1. 模式的结构

策略模式的主要角色如下。

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

UML类图:

UML序列图:

2. 模式的实现

策略模式的实现代码如下:

 

package strategy;
public class StrategyPattern
{
    public static void main(String[] args)
    {
        Context c=new Context();
        Strategy s=new ConcreteStrategyA();
        c.setStrategy(s);
        c.strategyMethod();
        System.out.println("-----------------");
        s=new ConcreteStrategyB();
        c.setStrategy(s);
        c.strategyMethod();
    }
}
//抽象策略类
interface Strategy
{   
    public void strategyMethod();    //策略方法
}
//具体策略类A
class ConcreteStrategyA implements Strategy
{
    public void strategyMethod()
    {
        System.out.println("具体策略A的策略方法被访问!");
    }
}
//具体策略类B
class ConcreteStrategyB implements Strategy
{
  public void strategyMethod()
  {
      System.out.println("具体策略B的策略方法被访问!");
  }
}
//环境类
class Context
{
    private Strategy strategy;
    public Strategy getStrategy()
    {
        return strategy;
    }
    public void setStrategy(Strategy strategy)
    {
        this.strategy=strategy;
    }
    public void strategyMethod()
    {
        strategy.strategyMethod();
    }
}

程序运行结果如下:

具体策略A的策略方法被访问!
-----------------
具体策略B的策略方法被访问!

3 策略模式的应用实例

通用实现

策略模式代码的一般通用实现:

策略接口

package strategy.examp01;
 
//策略接口
public interface IStrategy {
    //定义的抽象算法方法 来约束具体的算法实现方法
    public void algorithmMethod();
}

具体的策略实现:

package strategy.examp01;
 
// 具体的策略实现
public class ConcreteStrategy implements IStrategy {
    //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy method...");
    }
}
package strategy.examp01;
 
 // 具体的策略实现2
public class ConcreteStrategy2 implements IStrategy {
     //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy2 method...");
    }
}

策略上下文:

package strategy.examp01;
 
/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一个策略实现的引用
    private IStrategy strategy;
    //使用构造器注入具体的策略类
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
 
    public void contextMethod(){
        //调用策略实现的方法
        strategy.algorithmMethod();
    }
}

外部客户端:

package strategy.examp01;
 
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建具体测策略实现
        IStrategy strategy = new ConcreteStrategy2();
        //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext ctx = new StrategyContext(strategy);
        //3.调用上下文对象的方法来完成对具体策略实现的回调
        ctx.contextMethod();
    }
}

报价管理的例子

公共报价策略接口:

package strategy.examp02;
 
import java.math.BigDecimal;
//报价策略接口
public interface IQuoteStrategy {
    //获取折后价的价格
    BigDecimal getPrice(BigDecimal originalPrice);
}

新客户报价策略实现:

package strategy.examp02;
 
import java.math.BigDecimal;
//新客户的报价策略实现类
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }
}

老客户报价策略实现:

package strategy.examp02;
 
import java.math.BigDecimal;
//老客户的报价策略实现
public class OldCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户享有9折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

VIP客户报价策略实现:

package strategy.examp02;
 
import java.math.BigDecimal;
//VIP客户的报价策略实现
public class VIPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户享有8折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

报价上下文:

package strategy.examp02;
 
import java.math.BigDecimal;
//报价上下文角色
public class QuoteContext {
    //持有一个具体的报价策略
    private IQuoteStrategy quoteStrategy;
 
    //注入报价策略
    public QuoteContext(IQuoteStrategy quoteStrategy){
        this.quoteStrategy = quoteStrategy;
    }
 
    //回调具体报价策略的方法
    public BigDecimal getPrice(BigDecimal originalPrice){
        return quoteStrategy.getPrice(originalPrice);
    }
}

外部客户端:

package strategy.examp02;
 
import java.math.BigDecimal;
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建老客户的报价策略
        IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();
 
        //2.创建报价上下文对象,并设置具体的报价策略
        QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);
 
        //3.调用报价上下文的方法
        BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
 
        System.out.println("折扣价为:" +price);
    }
}

控制台输出:

恭喜!老客户享有9折优惠!
折扣价为:90.00

这个时候,商场营销部新推出了一个客户类型--MVP用户(Most Valuable Person),可以享受折扣7折优惠,那该怎么办呢?

这个很容易,只要新增一个报价策略的实现,然后外部客户端调用的时候,创建这个新增的报价策略实现,并设置到策略上下文就可以了,对原来已经实现的代码没有任何的改动。

MVP用户的报价策略实现:

package strategy.examp02;
 
import java.math.BigDecimal;
//MVP客户的报价策略实现
public class MVPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("哇偶!MVP客户享受7折优惠!!!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

外部客户端:

package strategy.examp02;
 
import java.math.BigDecimal;
//外部客户端
public class Client {
    public static void main(String[] args) {
        //创建MVP客户的报价策略
        IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy();
 
        //创建报价上下文对象,并设置具体的报价策略
        QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy);
 
        //调用报价上下文的方法
        BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
 
        System.out.println("折扣价为:" +price);
    }
}

控制台输出:

哇偶!MVP客户享受7折优惠!!!
折扣价为:70.00

策略和上下文的实例

在策略模式中,一般情况下都是上下文持有策略的引用,以进行对具体策略的调用。但具体的策略对象也可以从上下文中获取所需数据,可以将上下文当做参数传入到具体策略中,具体策略通过回调上下文中的方法来获取其所需要的数据。

下面我们演示这种情况:

  在跨国公司中,一般都会在各个国家和地区设置分支机构,聘用当地人为员工,这样就有这样一个需要:每月发工资的时候,中国国籍的员工要发人民币,美国国籍的员工要发美元,英国国籍的要发英镑。

 //支付策略接口
 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 元!

  那我们来比较一下上面两种实现方式:

  扩展上下文的实现:

    优点:具体的策略实现风格很是统一,策略实现所需要的数据都是从上下文中获取的,在上下文中添加的数据,可以视为公共的数据,其他的策略实现也可以使用。

    缺点:很明显如果某些数据只是特定的策略实现需要,大部分的策略实现不需要,那这些数据有“浪费”之嫌,另外如果每次添加算法数据都扩展上下文,很容易导致上下文的层级很是复杂。

  在具体的策略实现上添加所需要的数据的实现:

    优点:容易想到,实现简单

    缺点:与其他的策略实现风格不一致,其他的策略实现所需数据都是来自上下文,而这个策略实现一部分数据来自于自身,一部分数据来自于上下文;外部在使用这个策略实现的时候也和其他的策略实现不一致了,难以以一个统一的方式动态的切换策略实现。

 

 

 

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐