策略模式

策略模式UML类图

策略模式UML类图

策略模式java实现

/**
* Strategy interface.
屠龙策略
*/
@FunctionalInterface
public interface DragonSlayingStrategy {

void execute();

}

/**
* Spell strategy.
法术策略
*/
public class SpellStrategy implements DragonSlayingStrategy {

private static final Logger LOGGER = LoggerFactory.getLogger(SpellStrategy.class);

@Override
public void execute() {
//你投下了瓦解的魔咒,龙在一堆尘土中蒸发了!
LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!");
}

}

/**
* Projectile strategy.
弹丸策略。
*/
public class ProjectileStrategy implements DragonSlayingStrategy {

private static final Logger LOGGER = LoggerFactory.getLogger(ProjectileStrategy.class);

@Override
public void execute() {
//您用神奇的弓箭射击了龙,它掉在了地上!
LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!");
}
}

/**
* Melee strategy.
近战策略。
*/
public class MeleeStrategy implements DragonSlayingStrategy {

private static final Logger LOGGER = LoggerFactory.getLogger(MeleeStrategy.class);

@Override
public void execute() {
//用您的神剑切断龙的头!
LOGGER.info("With your Excalibur you sever the dragon's head!");
}
}

/**
* DragonSlayer uses different strategies to slay the dragon.
DragonSlayer使用不同的策略来杀死龙。
*/
public class DragonSlayer {

private DragonSlayingStrategy strategy;

public DragonSlayer(DragonSlayingStrategy strategy) {
this.strategy = strategy;
}

public void changeStrategy(DragonSlayingStrategy strategy) {
this.strategy = strategy;
}

public void goToBattle() {
strategy.execute();
}
}

/**
*
* <p>The Strategy pattern (also known as the policy pattern) is a software design pattern that
* enables an algorithm's behavior to be selected at runtime.</p>
*
* <p>Before Java 8 the Strategies needed to be separate classes forcing the developer
* to write lots of boilerplate code. With modern Java it is easy to pass behavior
* with method references and lambdas making the code shorter and more readable.</p>
*
* <p>In this example ({@link DragonSlayingStrategy}) encapsulates an algorithm. The containing
* object ({@link DragonSlayer}) can alter its behavior by changing its strategy.</p>
<p>策略模式(也称为策略模式)是一种软件设计模式,可以在运行时选择算法的行为。</ p>
<p>在Java 8之前,策略必须是单独的类,从而迫使开发人员编写大量样板代码。 使用现代Java,可以轻松地通过方法引用和lambda传递行为,从而使代码更短,更易读。</ p>
<p>在此示例({@link DragonSlayingStrategy})中封装了一种算法。 包含对象({@link DragonSlayer})可以通过更改其策略来更改其行为。</ p>
*
*/
public class App {

private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
// GoF Strategy pattern
LOGGER.info("Green dragon spotted ahead!");
var dragonSlayer = new DragonSlayer(new MeleeStrategy());
dragonSlayer.goToBattle();
LOGGER.info("Red dragon emerges.");
dragonSlayer.changeStrategy(new ProjectileStrategy());
dragonSlayer.goToBattle();
LOGGER.info("Black dragon lands before you.");
dragonSlayer.changeStrategy(new SpellStrategy());
dragonSlayer.goToBattle();

// Java 8 Strategy pattern
LOGGER.info("Green dragon spotted ahead!");
dragonSlayer = new DragonSlayer(
() -> LOGGER.info("With your Excalibur you severe the dragon's head!"));
dragonSlayer.goToBattle();
LOGGER.info("Red dragon emerges.");
dragonSlayer.changeStrategy(() -> LOGGER.info(
"You shoot the dragon with the magical crossbow and it falls dead on the ground!"));
dragonSlayer.goToBattle();
LOGGER.info("Black dragon lands before you.");
dragonSlayer.changeStrategy(() -> LOGGER.info(
"You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"));
dragonSlayer.goToBattle();
}
}

应用场景

Java Comparator 中的策略模式

java.util.Comparator 接口是比较器接口,可以通过 Collections.sort(List,Comparator) 和 Arrays.sort(Object[],Comparator) 对集合和数据进行排序,下面为示例程序
一个学生类,有两个属性 id 和 name

@Data
@AllArgsConstructor
public class Student {
private Integer id;
private String name;

@Override
public String toString() {
return "{id=" + id + ", name='" + name + "'}";
}
}

实现两个比较器,比较器实现了 Comparator 接口,一个升序,一个降序

// 降序
public class DescSortor implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o2.getId() - o1.getId();
}
}

// 升序
public class AscSortor implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() - o2.getId();
}
}

通过 Arrays.sort() 对数组进行排序

public class Test1 {
public static void main(String[] args) {
Student[] students = {
new Student(3, "张三"),
new Student(1, "李四"),
new Student(4, "王五"),
new Student(2, "赵六")
};
toString(students, "排序前");

Arrays.sort(students, new AscSortor());
toString(students, "升序后");

Arrays.sort(students, new DescSortor());
toString(students, "降序后");
}

public static void toString(Student[] students, String desc){
for (int i = 0; i < students.length; i++) {
System.out.print(desc + ": " +students[i].toString() + ", ");
}
System.out.println();
}
}

输出

排序前: {id=3, name='张三'}, 排序前: {id=1, name='李四'}, 排序前: {id=4, name='王五'}, 排序前: {id=2, name='赵六'}, 
升序后: {id=1, name='李四'}, 升序后: {id=2, name='赵六'}, 升序后: {id=3, name='张三'}, 升序后: {id=4, name='王五'},
降序后: {id=4, name='王五'}, 降序后: {id=3, name='张三'}, 降序后: {id=2, name='赵六'}, 降序后: {id=1, name='李四'},

通过 Collections.sort() 对集合List进行排序

public class Test2 {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student(3, "张三"),
new Student(1, "李四"),
new Student(4, "王五"),
new Student(2, "赵六")
);
toString(students, "排序前");

Collections.sort(students, new AscSortor());
toString(students, "升序后");

Collections.sort(students, new DescSortor());
toString(students, "降序后");
}

public static void toString(List<Student> students, String desc) {
for (Student student : students) {
System.out.print(desc + ": " + student.toString() + ", ");
}
System.out.println();
}
}

输出

排序前: {id=3, name='张三'}, 排序前: {id=1, name='李四'}, 排序前: {id=4, name='王五'}, 排序前: {id=2, name='赵六'}, 
升序后: {id=1, name='李四'}, 升序后: {id=2, name='赵六'}, 升序后: {id=3, name='张三'}, 升序后: {id=4, name='王五'},
降序后: {id=4, name='王五'}, 降序后: {id=3, name='张三'}, 降序后: {id=2, name='赵六'}, 降序后: {id=1, name='李四'},

我们向 Collections.sort() 和 Arrays.sort() 分别传入不同的比较器即可实现不同的排序效果(升序或降序)
这里 Comparator 接口充当了抽象策略角色,两个比较器 DescSortor 和 AscSortor 则充当了具体策略角色,Collections 和 Arrays 则是环境角色

Spring Resource 中的策略模式

Spring 把所有能记录信息的载体,如各种类型的文件、二进制流等都称为资源,譬如最常用的Spring配置文件。
在 Sun 所提供的标准 API 里,资源访问通常由 java.NET.URL 和文件 IO 来完成,尤其是当我们需要访问来自网络的资源时,通常会选择 URL 类。
URL 类可以处理一些常规的资源访问问题,但依然不能很好地满足所有底层资源访问的需要,比如,暂时还无法从类加载路径、或相对于 ServletContext 的路径来访问资源,虽然 Java 允许使用特定的 URL 前缀注册新的处理类(例如已有的 http: 前缀的处理类),但是这样做通常比较复杂,而且 URL 接口还缺少一些有用的功能,比如检查所指向的资源是否存在等。
Spring 改进了 Java 资源访问的策略,Spring 为资源访问提供了一个 Resource 接口,该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。

public interface Resource extends InputStreamSource {
boolean exists(); // 返回 Resource 所指向的资源是否存在
boolean isReadable(); // 资源内容是否可读
boolean isOpen(); // 返回资源文件是否打开
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException; // 返回资源对应的 File 对象
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription(); // 返回资源的描述信息
}

Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。
Spring 为 Resource 接口提供的部分实现类如下:
UrlResource:访问网络资源的实现类。
ClassPathResource:访问类加载路径里资源的实现类。
FileSystemResource:访问文件系统里资源的实现类。
ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:
InputStreamResource:访问输入流资源的实现类。
ByteArrayResource:访问字节数组资源的实现类。
WritableResource:写资源文件
这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。
它们之间的类关系如下所示:
spring-resource
可以看到 AbstractResource 资源抽象类实现了 Resource 接口,为子类通用的操作提供了具体实现,非通用的操作留给子类实现,所以这里也应用了模板方法模式。(只不过缺少了模板方法)
Resource 不仅可在 Spring 的项目中使用,也可直接作为资源访问的工具类使用。意思是说:即使不使用 Spring 框架,也可以使用 Resource 作为工具类,用来代替 URL。
譬如我们可以使用 UrlResource 访问网络资源。
也可以通过其它协议访问资源,file: 用于访问文件系统;http: 用于通过 HTTP 协议访问资源;ftp: 用于通过 FTP 协议访问资源等

public class Test {
public static void main(String[] args) throws IOException {
UrlResource ur = new UrlResource("http://image.laijianfeng.org/hello.txt");

System.out.println("文件名:" + ur.getFilename());
System.out.println("网络文件URL:" + ur.getURL());
System.out.println("是否存在:" + ur.exists());
System.out.println("是否可读:" + ur.isReadable());
System.out.println("文件长度:" + ur.contentLength());

System.out.println("\n--------文件内容----------\n");
byte[] bytes = new byte[47];
ur.getInputStream().read(bytes);
System.out.println(new String(bytes));
}
}

输出的内容如下,符合预期

文件名:hello.txt
网络文件URL:http://image.laijianfeng.org/hello.txt
是否存在:true
是否可读:true
文件长度:47

--------文件内容----------

hello world!
welcome to http://laijianfeng.org

Spring Bean 实例化中的策略模式

Spring实例化Bean有三种方式:构造器实例化、静态工厂实例化、实例工厂实例化
譬如通过构造器实例化bean的XML示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="person" class="com.demo.Person"></bean>

<bean id="personWithParam" class="com.demo.Person">
<constructor-arg name="name" value="小旋锋"/>
</bean>

<bean id="personWirhParams" class="com.demo.Person">
<constructor-arg name="name" value="小旋锋"/>
<constructor-arg name="age" value="22"/>
</bean>
</beans>

具体实例化Bean的过程中,Spring中角色分工很明确,创建对象的时候先通过 ConstructorResolver 找到对应的实例化方法和参数,再通过实例化策略 InstantiationStrategy 进行实例化,根据创建对象的三个分支( 工厂方法、有参构造方法、无参构造方法 ), InstantiationStrategy 提供了三个接口方法:

public interface InstantiationStrategy {
// 默认构造方法
Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) throws BeansException;

// 指定构造方法
Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Constructor<?> ctor,
Object[] args) throws BeansException;

// 指定工厂方法
Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Object factoryBean,
Method factoryMethod, Object[] args) throws BeansException;
}

InstantiationStrategy 为实例化策略接口,扮演抽象策略角色,有两种具体策略类,分别为 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy
spring-instantiation
在 SimpleInstantiationStrategy 中对这三个方法做了简单实现,如果工厂方法实例化直接用反射创建对象,如果是构造方法实例化的则判断是否有 MethodOverrides,如果有无 MethodOverrides 也是直接用反射,如果有 MethodOverrides 就需要用 cglib 实例化对象,SimpleInstantiationStrategy 把通过 cglib 实例化的任务交给了它的子类 CglibSubclassingInstantiationStrategy。

项目实战

患者绑定医生