2、5策略模式
定义
定义一组算法,分别将每个算法封装起来,并且使他们之间可以互换。此模式让算法的变化(完成的都是相同的工作,只是实现不同),不会影响到使用算法的客户(封装了变化)。
通用类图
三个角色(类图描述)
策略模式使用的是面向对象的继承和多态机制。
1)Context封装角色
也叫做上下文角色,起承上启下封装作用,屏蔽高层模式对策略、算法的直接访问,封装可能存在的变化。(注意是聚合关系)
在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化地减轻了客户端的职责。
这个并不完美,可查看” 设计模式(三)创建型模式(对象创建型模式)抽象工厂-ABSTRACT FACTORY”中的”反射”。
2)Strategy抽象策略角色
策略、算法家族的抽象,通常为接口(封装),定义每个策略或算法(重复的)必须具有的方法和属性。类图中的AlgorithmInterface是运算法则接口的意思。此时,策略模式看上去更像是模板方法模式。
策略模式的Strategy层次为Context定义了一系列的可供重用的算法或行为。继承有助于吸取这些算法中的公共功能。
当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
3)ConcreteStrategy具体策略角色
具体策略角色通常由一组封装了算法的类来担任,实现抽象策略中的操作,该类含有具体的算法,这些类之间可以根据需要自由替换。
案例
public class Client2 {
/**
* 高层模块
* @param args
*/
public static void main(String[] args) {
//声明一个具体的策略
Stratery stratery = new ConcreteStratery1();
//声明上下文对象
Context context = new Context(stratery);
//执行封装后的方法
context.doAnything();
}
}
/**
* 抽象的策略角色
*/
interface Stratery{
//策略模式的运算法则
void doSomething();
}
/**
* 具体策略角色
*/
class ConcreteStratery1 implements Stratery{
@Override
public void doSomething() {
System.out.println("具体策略1的运算法则");
}
}
class ConcreteStratery2 implements Stratery{
@Override
public void doSomething() {
System.out.println("具体策略2的运算法则");
}
}
/**
* 封装角色。
* 封装角色的责任是保证策略时可以相互替换。
* 策略模式的重点就是封装角色,它是借用了代理模式的思路,
* 它和代理模式的差别就是策略模式的封装角色和被封装的策略类不用是同一个接口,
* 如果是同一个接口那就成为了代理模式。
*/
class Context{
//抽象策略,维护一个引用。
private Stratery stratery = null;
//构造函数设置具体策略
public Context(Stratery stratery) {
this.stratery = stratery;
}
//封装后的策略方法
public void doAnything(){
this.stratery.doSomething();
}
}
案例扩展(加减法)(逐渐优化)
案例1
public class Client31 {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int a = 1;
int b = 2;
String add = "+";
System.out.println(calculator.exec(a,b,add));
String sub = "-";
System.out.println(calculator.exec(a,b,sub));
}
}
class Calculator{
private final static String ADD_SYSBOL = "+";
private final static String SUB_SYSBOL = "-";
public int exec(int a,int b,String symbol){
int result = 0;
if(ADD_SYSBOL.equals(symbol)){
result = this.add(a,b);
}else if(SUB_SYSBOL.equals(symbol)){
result = this.sub(a,b);
}
return result;
}
private int add(int a,int b){
return a + b;
}
private int sub(int a,int b){
return a - b;
}
}
案例2
public class Client32 {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int a = 1;
int b = 2;
String add = "+";
System.out.println(calculator.exec(a,b,add));
String sub = "-";
System.out.println(calculator.exec(a,b,sub));
}
}
class Calculator{
private final static String ADD_SYSBOL = "+";
private final static String SUB_SYSBOL = "-";
public int exec(int a,int b,String symbol){
return symbol.equals(ADD_SYSBOL) ? a + b : a - b;
}
private int add(int a,int b){
return a + b;
}
private int sub(int a,int b){
return a - b;
}
}
案例3
public class Client33 {
private final static String ADD_SYSBOL = "+";
private final static String SUB_SYSBOL = "-";
public static void main(String[] args) {
//输入的两个参数是数字
int a = 1;
int b = 2;
String symbol = "+";
Context context;
if(ADD_SYSBOL.equals(symbol)){
context = new Context(new Add());
System.out.println(context.exec(a,b,symbol));
}
}
}
interface Calculator{
int exec(int a,int b);
}
class Add implements Calculator{
@Override
public int exec(int a, int b) {
return a + b;
}
}
class Sub implements Calculator{
@Override
public int exec(int a, int b) {
return a - b;
}
}
class Context{
private Calculator calculator;
public Context(Calculator calculator) {
this.calculator = calculator;
}
public int exec(int a, int b,String symbol) {
return this.calculator.exec(a,b);
}
}
案例4(策略枚举)
/**
* 策略枚举。
* 把原有定义在抽象策略种的方法移植到枚举中,每个枚举成员就成为一个具体策略。
* 枚举定义:
* 它是一个枚举。
* 它是一个浓缩了的策略模式的枚举。
* 注意:策略枚举是一个非常优秀和方便的模式,但是它受枚举类型的限制,每个枚举项都是public、final、static的,
* 扩展性收到了一定的约束,因此在系统开发中,策略枚举一般担当不经常发生变化的角色。
*/
public class Client34 {
public static void main(String[] args) {
//输入的两个参数是数字
int a = 1;
int b = 2;
//获得结果集
System.out.println(Calculator.ADD.exec(a,b));
System.out.println(Calculator.SUB.exec(a,b));
}
}
enum Calculator{
ADD("+"){
@Override
public int exec(int a, int b) {
return a + b;
}
},
SUB("-"){
@Override
public int exec(int a, int b) {
return a - b;
}
};
String value;
//定义成员值类型
Calculator(String _value){
this.value = _value;
}
//获得枚举成员的值
public String getValue() {
return this.value;
}
//声明一个抽象函数
public abstract int exec(int a,int b);
}
优点
1)算法可以自由切换
这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供”可自由切换”的策略。
2)避免使用多重条件判断
使用策略模式后,可以由其他模式决定采用何种策略,策略模式家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。
3)扩展性良好
在现有的系统中增加一个策略只要实现接口就可以了,基本上可以在不改变原有代码的基础上进行扩展,类似于一个可反复拆卸的插件,这大大地符合了OCP(开闭)原则。
4)简化单元测试
因为每个算法都有自己的类,可以通过自己的接口单独测试。
缺点
1)策略类数量增多
每一个策略类都是一个类,复用的可能性很小,类数量增多。维护各个策略类会给开发带来额外开销,可能大家在这方面都有经验:一般来说,策略类的数量超过5个,就比较令人头疼了。
2)所有的策略类都需要对外暴露
必须对客户端(调用者)暴露所有的策略类,因为使用哪种策略是由客户端来决定的,因此,客户端应该知道有什么策略,并且了解各种策略之间的区别,否则,后果很严重。例如,有一个排序算法的策略模式,提供了快速排序、冒泡排序、选择排序这三种算法,客户端在使用这些算法之前,是不是先要明白这三种算法的适用情况?再比如,客户端要使用一个容器,有链表实现的,也有数组实现的,客户端是不是也要明白链表和数组有什么区别?就这一点来说是有悖于迪米特法则的。
上层模块必须知道有哪些策略,然后才能决定使用哪一个策略,这与迪米特法则是相违背的。这是原装策略模式的一个缺点,可以使用其他模式来修正这个缺陷,(一起使用)如工厂方法模式、代理模式或享元模式。
适用场景
1)多个类只有在算法或行为上稍有不同的场景。
2)算法需要自由切换的场景。
例如,算法的选择是由使用者决定的,或者算法始终在进化,特别是一些站在技术前沿的行业,连业务专家都无法给你保证这样的系统规则能够存在多长时间,在这种情况下策略模式是你最好的助手。
3)需要屏蔽算法规则的场景。
例如,有一个排序算法的策略模式,提供了快速排序、冒泡排序、选择排序这三种算法,客户端在使用这些算法之前,是不是先要明白这三种算法的适用情况?再比如,客户端要使用一个容器,有链表实现的,也有数组实现的,客户端是不是也要明白链表和数组有什么区别?太多的算法只要知道一个名字就可以了,传递相关的数字进来,反馈一个运算结果就OK了。
注意事项
1)如果系统中的一个策略家族的具体策略数量超过4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题,否则日后的系统维护就会成为一个烫手山芋,谁都不想接。
2)面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
标题:设计模式(五)行为型模式(对象行为型模式)策略模式-TEMPLATE METHOD
作者:yazong
地址:https://blog.llyweb.com/articles/2020/05/30/1590808617986.html