• 了解策略模式


    如果大家觉得文章有错误内容,欢迎留言或者私信讨论~

      策略模式最常见的应用场景是利用它来避免冗长的 if-else 或者 switch 分支判断。就像之前写的博文里提到的一样,设计模式的实现与原理都非常简单,难点在于如何将设计模式应用到实际的业务场景里,用它去解决实际问题。
      那么话不多说,来了解策略模式吧。

    原理与实现

    Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

      翻译成中文就是:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
      策略模式是起到解耦的作用,它解耦的是策略的定义、创建、使用这三部分。

    1. 策略的定义

      策略的定义都非常简单,通常都是实现相同的接口,所以,客户端的代码(使用者)是基于接口而非基于实现编程,可以灵活地替换不同的策略。

    public interface Strategy {
    	void algorithmInterface();
    }
    
    public class ConcreteStrategyA implements Strategy {
    	@Override
    	public void algorithmInterface() {
    		//具体的算法...
    	}
    }
    
    public class ConcreteStrategyB implements Strategy {
    	@Override
    	public void algorithmInterface() {
    		//具体的算法...
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. 策略的创建

      因为策略模式是包含了一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。我们可以把根据 type 创建策略的逻辑抽离出来,放到工厂类中:

    public class StrategyFactory {
    	private static final Map<String, Strategy> strategies = new HashMap<>();
    		
    	static {
    		strategies.put("A", new ConcreteStrategyA());
    		strategies.put("B", new ConcreteStrategyB());
    	}
    	
    	public static Strategy getStrategy(String type) {
    		if (type == null || type.isEmpty()) {
    			throw new IllegalArgumentException("type should not be empty.");
    		}
    		return strategies.get(type);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

      一般来讲,如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用 getStrategy() 的时候,都创建一个新的策略对象。针对这种情况,我们可以使用上面这种工厂类的实现方式,事先创建好每个策略对象,缓存到工厂类中,用的时候直接返回。

      相反,如果策略类是有状态的,根据业务场景的需要,我们希望每次从工厂方法中,获得的都是新创建的策略对象,而不是缓存好可共享的策略对象,那我们就需要按照如下方式来实现策略工厂类。

    // 注意代码是放到什么环境下执行的,是否需要实现并发控制
    public class StrategyFactory {
    	public static Strategy getStrategy(String type) {
    		if (type == null || type.isEmpty()) {
    			throw new IllegalArgumentException("type should not be empty.");
    		}
    
    		if (type.equals("A")) {
    			return new ConcreteStrategyA();
    		} else if (type.equals("B")) {
    			return new ConcreteStrategyB();
    		} 
    		return null;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3. 策略的使用

      上面我们也讲到,策略是有“type”的,我们实现不知道会使用那个策略,而是在程序运行期间,根据配置、用户输入、计算结果等这些不确定因素的"type",动态决定使用哪种策略:

    // 策略接口:EvictionStrategy
    // 策略类:LruEvictionStrategy、FifoEvictionStrategy、LfuEvictionStrategy...
    // 策略工厂:EvictionStrategyFactory
    
    public class UserCache {
    	private Map<String, User> cacheData = new HashMap<>();
    	private EvictionStrategy eviction;
    	public UserCache(EvictionStrategy eviction) {
    		this.eviction = eviction;
    	} 
    	
    	//...
    }
    
    // 运行时动态确定,根据配置文件的配置决定使用哪种策略
    public class Application {
    	public static void main(String[] args) throws Exception {
    		EvictionStrategy evictionStrategy = null;
    		Properties props = new Properties();
    		props.load(new FileInputStream("./config.properties"));
    		String type = props.getProperty("eviction_type");
    		evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
    		UserCache userCache = new UserCache(evictionStrategy);
    		//...
    	}
    }
    
    // 非运行时动态确定,在代码中指定使用哪种策略
    public class Application {
    	public static void main(String[] args) {
    		//...
    		EvictionStrategy evictionStrategy = new LruEvictionStrategy();
    		UserCache userCache = new UserCache(evictionStrategy);
    		//.....
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    如何利用策略避免分支判断呢

      那么我们在上面讲到了策略模式主要是来避免冗长的分支判断的,那么该如何实现呢?我们来看一个场景:

    public class OrderService {
    	public double discount(Order order) {
    		double discount = 0.0;
    		OrderType type = order.getType();
    	
    		if (type.equals(OrderType.NORMAL)) { // 普通订单
    			//...省略折扣计算算法代码
    		} else if (type.equals(OrderType.GROUPON)) { // 团购订单
    			//...省略折扣计算算法代码
    		} else if (type.equals(OrderType.PROMOTION)) { // 促销订单
    			//...省略折扣计算算法代码
    		}
    		return discount;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

      我们使用策略模式对上面的代码重构,将不同类型订单的打折策略设计成策略类,并由工厂类来负责创建策略对象。具体的代码如下所示:

    // 策略的定义
    public interface DiscountStrategy {
    	double calDiscount(Order order);
    }
    // 省略NormalDiscountStrategy、GrouponDiscountStrategy、PromotionDiscountStrategy.....
    
    // 策略的创建
    public class DiscountStrategyFactory {
    	private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();
    	
    	static {
    		strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());
    		strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());
    		strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());
    	}
    
    	public static DiscountStrategy getDiscountStrategy(OrderType type) {
    		return strategies.get(type);
    	}
    }
    
    // 策略的使用
    public class OrderService {
    	public double discount(Order order) {
    		OrderType type = order.getType();
    		DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
    		return discountStrategy.calDiscount(order);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

      重构之后的代码就没有了 if-else 分支判断语句了。实际上,这得益于策略工厂类。在工厂类中,我们用 Map 来缓存策略,根据 type 直接从 Map 中获取对应的策略,从而避免 ifelse 分支判断逻辑。等后面讲到使用状态模式来避免分支判断逻辑的时候,你会发现,它们使用的是同样的套路。本质上都是借助“查表法”,根据 type 查表(代码中的strategies 就是表)替代根据 type 分支判断。

     &emksp;但如果我们是需要每次都创建不同的策略对象,那么工厂里就会变成这样:

    public class DiscountStrategyFactory {
    	public static DiscountStrategy getDiscountStrategy(OrderType type) {
    		if (type == null) {
    			throw new IllegalArgumentException("Type should not be null.");
    		}
    		if (type.equals(OrderType.NORMAL)) {
    			return new NormalDiscountStrategy();
    		} else if (type.equals(OrderType.GROUPON)) {
    			return new GrouponDiscountStrategy();
    		} else if (type.equals(OrderType.PROMOTION)) {
    			return new PromotionDiscountStrategy();
    		}
    		return null;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

      那么可以怎么解决呢?可以通过一个配置文件或者自定义的 annotation 来标注都有哪些策略类;策略工厂类读取配置文件或者搜索被 annotation 标注的策略类,然后通过反射了动态地加载这些策略类、创建策略对象;当我们新添加一个策略的时候,只需要将这个新添加的策略类添加到配置文件或者用 annotation 标注来解决。

  • 相关阅读:
    开源LC3编解码器测试Demo
    5.11-5.12Union-Find算法详解
    ifconfig
    如何压缩jpg图片的大小
    如何通过port-forward命令在本地访问 k8s 集群服务
    Pickle反序列化学习
    Python 环境构建最佳实践:Mamba + Conda + PIP
    Apache Shiro 认证绕过漏洞(CVE-2020-1957 )漏洞复现
    【新员工座位安排系统】python实现-附ChatGPT解析
    基于视觉AI的管道高后果区预警系统
  • 原文地址:https://blog.csdn.net/qq_43654226/article/details/126772565