什么是策略模式?

  • 策略模式(Strategy Pattern)就是对算法的封装,是把使用算法的职责和算法本身实现分割开来,委派给不同的对象管理。它将定义的算法家族分别封装起来,让他们之间可以相互替换从而让算法的变化不会影响到使用算法的用户,属于行为设计模式。

Define a family of algorithms, encapsulate each one, and make them interchangeable
本质就是使用面向对象的继承和多态机制,从而实现同一行为在不同的应用场景下具有不同的实现。

Strategy模式的角色构成

  1. Strategy Interface 策略接口:用于对算法行为的抽象,规定策略或者算法的行为。
  2. ConcreteStrategy 具体策略实:具体的算法和策略实现。
  3. Context 上下文角色:用于屏蔽高层模块对策略,算法的直接访问,封装可能存在的变化。通过Context来进行对具体策略接口实现的调用,实现了算法策略具体实现的再次封装。拥有Strategy的成员变量或者其他的策略接口。对于需要使用到哪个策略可以在构造器中指定。

案例实现

对于我们使用的电商平台,当我们支付时会出现各种各种支付方式,包括支付宝,微信,京东支付等方式,下面通过策略模式来实现不同的支付方式。

1.Strategy接口

package strategy2;
//Strategy 抽象出支付方式的策略接口,抽象支付方法为抽象方法。
public interface PayStrategy {
    public void pay();

}

2.ConcreteStrategy 定义该支付策略的具体实现,包括支付宝支付,京东支付以及默认支付三种具体的支付策略的实现。

支付宝支付实现类1
package strategy2;

public class AliPayImpl implements PayStrategy{
    @Override
    public void pay() {
        System.out.println("使用支付宝支付!");
    }
}
京东支付实现类2
package strategy2;

public class JdPayImpl implements PayStrategy{
    @Override
    public void pay() {
        System.out.println("京东支付!");
    }
}

默认支付实现类3
package strategy2;

public class DefaultPay implements PayStrategy{

    public DefaultPay(){}

    @Override
    public void pay() {
        System.out.println("默认支付方式");
    }
}

3.Context 上下文类(难点:为什么Client要通过Context来调用具体的策略实现类)

  • 在上下文类中进一步封装了策略算法的具体实现,使客户端不和具体的策略实现交互,而是通过Context对象来实现对具体策略方法的调用。在该类中首先应该包括策略接口,可以通过构造函数或者是传参的方式来传递测录接口。第二就是包含对具体策略的调用方法,进一步封装策略实现代码,该策略调用方法供客户端对象来调用。
  • 上下文对象中的两个重要组成部分:
  • (1)策略接口(在调用时通过多态的方式来指定,接口指向具体的实现类)
  • (2)策略实现的调用方法
package strategy2;
public class PayContext {
	//策略接口的成员变量
    private PayStrategy payStrategy;
	//通过构造方法确定具体策略实现
    public PayContext(PayStrategy payStrategy){
        this.payStrategy = payStrategy;
    }
	//具体策略的调用方法
    public void payStrategyExecute(){
        this.payStrategy.pay();
    }

}

4.客户端交互

package strategy2;
public class Main {
    public static void main(String[] args) {
    	//1.通过Context上下文调用具体的策略实现,客户端无需操作具体的策略实现算法,降低耦合性
        PayContext payContext = new PayContext(new AliPayImpl());
        payContext.payStrategyExecute();
  		//使用支付宝支付!
		
		//2.不通过Context对象来调用,直接使用客户端对象来调用方法
        PayStrategy jdPay = new JdPayImpl();
        jdPay.pay(); //jdPay.pay()是策略类实现的具体算法内容,这里涉及到了京东支付策略的具体算法,增加了客户端和策略类耦合性。
        
        //使用京东支付!
    }
}

策略模式通过Context对象屏蔽高层模块(客户端)对策略、算法的直接访问,封装了可能存在的变化。

对于后者如何理解封装了可能存在的变化
  • 加入对于上个案例的各个支付策略的具体实现需求现在发生了变化,在支付之前需要对支付交易进行记录,该怎么办?
  • 如果没有Context对象需要在每个具体的策略实现类中来添加交易记录的代码实现
public class DefaultPay implements PayStrategy{
    public DefaultPay(){}
    @Override
    public void pay() {
    	//添加交易记录代码
    	System.out.println("记录交易信息");
        System.out.println("默认支付方式");
    }
}
public class AliPay implements PayStrategy{
    public DefaultPay(){}
    @Override
    public void pay() {
    	//添加交易记录代码
    	System.out.println("记录交易信息");
        System.out.println("支付宝支付方式");
    }
}
...

  • 但是通过使用Context类,我们就可以把这个增加的需求代码添加到Context类中的策略算法的执行类中即可。不需要对每个策略类来进行添加,如下:
package strategy2;
public class PayContext {
	//策略接口的成员变量
    private PayStrategy payStrategy;
	//通过构造方法确定具体策略实现
    public PayContext(PayStrategy payStrategy){
        this.payStrategy = payStrategy;
    }
	//具体策略的调用方法
    public void payStrategyExecute(){
		//添加业务变更代码,交易记录
		System.out.println("记录交易信息");
        this.payStrategy.pay();
    }

}
  • 以上说明了为什么说Context封装了可能的变化,通过Context类来封装后期策略实现变化,不需要去进行每一个策略的更改。