在外出旅游时,很多时候的出行方式都不止一条,通常根据实际情况,比如目的地,预算,旅游时间等确定最适合的出行方式。在软件开发中,也常常会遇到类似的情况,实现某一个功能有多种途径,每一条途径对应一个算法,这时可以使用一种叫做策略模式的设计模式来进行设计。在策略模式中,可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法。
这里每一个封装的算法可以被称之为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类作为规则的定义,每种具体算法对应于一个具体策略类。
策略模式的主要目的是将算法的定义与使用分开,也就是将算法的行为和环境分开,将算法的定义放在专门的策略类中,每一个策略类封装了一种实现算法,使用算法的环境类针对抽象策略类进行编程,符合DIP(依赖倒转原则)。出现新算法时只需要定义一个新的具体策略类即可。
策略模式:定义一系列算法类,将每一个算法封装起来,并让他们可以相互替换。
策略模式也叫政策模式,是一种对象行为型模式。
Context
(环境类):使用算法的角色,解决了某个问题时可以采用的多种策略,在环境类维持一个抽象策略类的引用实例,用于定义所采用的策略Strategy
(抽象策略类):为支持的算法声明了抽象方法,是所有策略类的父类,可以是抽象类或具体类,也可以是接口,环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法ConcreteStrategy
(具体策略类):实现了抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理interface AbstarctStrategy
{
void algorithm();
}
这里定义为一个接口,只有一个抽象算法方法。
class ConcreteStrategy1 implements AbstarctStrategy
{
@Override
public void algorithm()
{
System.out.println("具体策略1");
}
}
class ConcreteStrategy2 implements AbstarctStrategy
{
@Override
public void algorithm()
{
System.out.println("具体策略2");
}
}
定义两个具体策略类,分别表示不同的算法。
class Context
{
private AbstarctStrategy strategy;
public void setStrategy(AbstarctStrategy strategy)
{
this.strategy = strategy;
}
public void algorithm()
{
strategy.algorithm();
}
}
通过setter注入具体策略类,在调用环境类的方法时通过抽象策略类调用其中的具体策略类的算法。
public static void main(String[] args)
{
Context context = new Context();
context.setStrategy(new ConcreteStrategy1());
context.algorithm();
context.setStrategy(new ConcreteStrategy2());
context.algorithm();
}
设计一个电影票打折系统,有三种不同的打折方式:学生可以享受8折优惠,10周岁以下儿童可以享受减免10元优惠,VIP可以享受半价优惠,使用策略模式进行设计。
设计如下:
MovieTicket
Discount
StudentDiscount
+VIPDiscount
+ChildrenDiscount
首先是抽象策略类:
interface Discount
{
double calculate(double price);
}
包含一个计算折扣的方法,接着是具体策略类:
class StudenDiscount implements Discount
{
@Override
public double calculate(double price)
{
System.out.println("学生票");
return price * 0.8;
}
}
class ChildrenDiscount implements Discount
{
@Override
public double calculate(double price)
{
System.out.println("儿童票");
return price - 10.0;
}
}
class VIPDiscount implements Discount
{
@Override
public double calculate(double price)
{
System.out.println("VIP票");
return price * 0.5;
}
}
三个不同的具体策略类表示三种不同的计算折扣方式,根据需要返回对应的折扣价格。
最后是环境类:
class MovieTicket
{
private Discount discount;
private double originalPrice;
public void setPrice(double price)
{
this.originalPrice = price;
}
public void setDiscount(Discount discount)
{
this.discount = discount;
}
public double getDicountPrice()
{
return discount.calculate(originalPrice);
}
}
环境类通过setPrice
设定电影票价格后,在通过setDiscount
注入具体策略类,最后使用getDiscountPrice
获取折扣后的价格。
测试:
public static void main(String[] args)
{
MovieTicket movieTicket = new MovieTicket();
movieTicket.setPrice(100.0);
movieTicket.setDiscount(new StudenDiscount());
System.out.println(movieTicket.getDicountPrice());
movieTicket.setDiscount(new VIPDiscount());
System.out.println(movieTicket.getDicountPrice());
movieTicket.setDiscount(new ChildrenDiscount());
System.out.println(movieTicket.getDicountPrice());
}
客户端需要明确知道这三种折扣,也就是打折方式由客户端指定,输出如下:
策略模式实用性强,扩展性好,是使用频率较高的设计模式,下面来看看JDK中的典型应用。
Java SE容器布局管理器就是策略模式的一个经典应用案例,基本结构如下:
JavaSE中用户需要对容器对象Container
进行布局,在程序运行期间由客户端动态决定一个Container
对象如何布局,Java提供了几种不同的布局方式:BorderLayout
,FlowLayout
,GridLayout
,GridBagLayout
,CardLayout
。在上图结构中:
Container
充当了环境角色Context
LayoutManager
充当了抽象策略角色LayoutManager
的各个子类充当了具体策略类Container
针对LayoutManager
进行编程,无须关心具体布局是什么,这样的设计符合里氏替换原则。
状态模式与策略模式很像,下面是两者的结构图:
两者的结构图很相似,但是实际上也有很多的不同:
Context
的使用不同:状态模式中每个状态持有Context
引用,实现状态切换,但是每个策略不持有Context
引用,策略只是被Context
使用else if
:多重选择语句不易维护,因为将选择算法的逻辑以及算法本事实现逻辑混在一起,硬编码在一个巨大的if/else if
中,使用策略模式可以避免这种结构如果觉得文章好看,欢迎点赞。
同时欢迎关注微信公众号:氷泠之路。