前言
还是按照惯例先说说策略模式的定义、结构。
策略模式定义
策略模式属于对象的行为模式。它的用意是针对一组算法,讲每一个算法封装到具有共同接口的独立的类中国,从而使他们可以相互替换。它可以使得算法可以在不影响客户端的情况下发生变化。
策略模式的结构:
策略模式又被叫做政策模式,举个简单的demo就可以完全了解策略模式的结构。
从上图可以看出,这个模式涉及到三个角色:
- 环境(Context)角色:持有一个Strategy类的引用;
- 抽象策略(Strategy)角色:这是一个抽象的角色,通常有一个接口或者抽象类实现,此角色给出所有的具体策略类所需的接口;
- 具体策略(ConcreateStrategy)角色:包装了相关的算法或者行为。
自定义策略模式
- 需求:
图书折扣价格计算:
假设现在有一个贩卖各类书籍的电子商务网站购物车,计算购物车里书的折扣。折扣规则:所有教材类图书每本一元折扣;连环画图书每本7%的促销折扣;非教材类的计算机图书每本有3%的折扣;其余图书无折扣。计算买价格为m的某一类书n本的折扣。
- 抽象设计:
看了上面的需求,大致可以有这么j几种解决方式:
- 所有的业务逻辑放在客户端里面,客户端利用if else语句决定使用哪一个算法,这样一来,客户端代码会变得复杂并且难以维护;
- 客户端可以利用继承的办法在子类里面实现不同的行为,但是这样子会使业务场景和行为紧密的耦合在一起,强耦合会使两者都不能单独演化;
- 使用策略模式。策略模式将行为和业务场景分割开来,负责业务场景的类维持和查询行为类,各种算法均在具体的策略类里面李彤。由于算法和业务场景独立开来,算法的增减、修改都不会影响环境和客户端。换句话说,当出现新的促销折扣或者现有的折扣政策出现变化时,只需要实现新的策略类,并且在客户端登记即可。
总的分析下来,三种模式还是策略模式最优,接下来看看具体的抽象类类图:
- 具体代码实现(我自己根据实际情况做了一定的修改)
Context:DiscountStrategy:/** * Created by miaomiao on 16/10/25. * 策略模式环境角色,持有一个Strategy引用 */ public class Context { private DiscountStrategy discountStrategy; public Context() { discountStrategy = new NoDiscountStrategy(new BigDecimal(0), 0); } public Context(BigDecimal price, int number, Float percent, BigDecimal discountPrice, BookTypeEnum typeEnum) { switch (typeEnum) { case TESTBOOK: discountStrategy = new FlatRateStrategy(price, number, discountPrice); break; case CARTOON: discountStrategy = new PercentageStrategy(price, number, percent); break; case COMPUTERSCIENCE: discountStrategy = new PercentageStrategy(price, number, percent); break; case OTHER: discountStrategy = new NoDiscountStrategy(price, number); break; default: discountStrategy = new NoDiscountStrategy(price, number); } } /** * 策略方法 */ public BigDecimal calculateDiscount() { return discountStrategy.calculateDiscount(); } }
NoDiscountStrategy:/** * Created by miaomiao on 16/10/25. */ public interface DiscountStrategy { public BigDecimal calculateDiscount(); }
FlatRateStrategy:/** * Created by miaomiao on 16/10/25. */ public class NoDiscountStrategy implements DiscountStrategy { private BigDecimal price; private int number; public NoDiscountStrategy(BigDecimal price, int number) { this.price = price; this.number = number; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public BigDecimal calculateDiscount() { return new BigDecimal(0); } }
PercentageStrategy:/** * Created by miaomiao on 16/10/25. */ public class FlatRateStrategy implements DiscountStrategy { private BigDecimal price; private int number; private BigDecimal discountPrice; public FlatRateStrategy(BigDecimal price, int number, BigDecimal discountPrice) { this.price = price; this.number = number; this.discountPrice = discountPrice; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public BigDecimal getDiscountPrice() { return discountPrice; } public void setDiscountPrice(BigDecimal discountPrice) { this.discountPrice = discountPrice; } public BigDecimal calculateDiscount() { <ul><li>}</li></ul> return discountPrice.multiply(new BigDecimal(number)); }
最后看看具体的调用:/** * Created by miaomiao on 16/10/25. */ public class PercentageStrategy implements DiscountStrategy { private BigDecimal price; private int number; private Float percent; public PercentageStrategy(BigDecimal price, int number, Float percent) { this.price = price; this.number = number; this.percent = percent; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public float getPercent() { return percent; } public void setPercent(float percent) { this.percent = percent; } public BigDecimal calculateDiscount() { return price.multiply(new BigDecimal(number)).multiply(new BigDecimal(percent)); } }
DiscountStrategyPatternTest:
/** * Created by miaomiao on 16/10/25. */ public class DiscountStrategyPatternTest { @Test public void run() { Context context = new Context(); System.out.println(context.calculateDiscount()); Context context1 = new Context(new BigDecimal(100), 100, null, new BigDecimal(1), BookTypeEnum.TESTBOOK); System.out.println(context1.calculateDiscount()); Context context2 = new Context(new BigDecimal(100), 100, 0.07f, null, BookTypeEnum.CARTOON); System.out.println(context2.calculateDiscount().setScale(2, BigDecimal.ROUND_HALF_EVEN)); Context context3 = new Context(new BigDecimal(100), 100, 0.04f, null, BookTypeEnum.COMPUTERSCIENCE); System.out.println(context3.calculateDiscount().setScale(2, BigDecimal.ROUND_HALF_EVEN)); } }
Java语言内部的使用
java语言使用了很多设计模式,包括策略模式。使用策略模式的例子可以在java.awt库和Swing库中看到。
AWT中的LayoutManager
java.awt类库需要在运行期间动态的由客户端决定一个Container对象怎么排列它所有的GUI构件。Java语言提供了几种不同的排列方式,包装在不同的类里:
- BorderLayout
- FlowLayout
- GridLayout
- GridBagLayout
- CardLayout
- 环境(Context)角色:在这里由Container类扮演;
- 抽象策略(Strategy)角色:这是一个抽象角色,由LayoutManager类扮演;
- 具体策略(ConcreateStrategy)角色:有BoderLayout等扮演,它们包装了不同的Layout行为。
Swing中的Border
在任何一个Swing构件上都可以画上边框(Border),比如panel、button等。Swing构件的基类是JComponent类,这个类负责为Swing构件画上边框。
JComponent类实现了paintBorder()方法,并且保持一个私有的对边框对象的引用。显然,JComponent类使用了策略模式来处理边框问题,同样设计三个角色:
- 环境(Context)角色:在这里由JComponent类扮演;
- 抽象策略(Strategy)角色:由Border类扮演;
- 具体策略(ConcreateStrategy)角色:由BevelBorder等扮演,它们包装了不同的边框行为。
后记
什么情况下使用策略模式
- 如果一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态的让一个对象在许多行为中选择一种行为;
- 一个系统需要动态的在几种算法中选择一种,那么可以将这些算法包装到一个个具体的算法类里面,而这些具体的算法类都是一个抽象算法的子类,换言之,这些具体算法均有统一的接口,由于多态性原则,客户端可以选择使用任何一个具体的算法类,并支持有一个数据类型是抽象算法类的对象;
- 一个系统的算法使用的数据不可以让客户端知道,策略模式可以避免让客户端涉及到不必要接触到的复杂的和至于算法相关的数据;
- 如果一个对象有很多行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现,此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并且体现面向对象设计的概念。
策略模式的优缺点
优点:
- 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或者行为族,恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代码;
- 策略模式提供了可以替换继承关系的办法;
- 使用策略模式可以避免使用多重条件转移语句;
- 客户端必须知道所有的策略类,并且自行决定使用哪一个策略类,真心觉得条件选择语句并没有减少;
- 策略模式造成了很多的策略类。