了解策略模式

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

  策略模式最常见的应用场景是利用它来避免冗长的 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() {
		//具体的算法...
	}
}

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);
	}
}

  一般来讲,如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用 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;
	}
}

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);
		//.....
	}
}

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

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

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;
	}
}

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

// 策略的定义
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);
	}
}

  重构之后的代码就没有了 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;
	}
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值