策略模式的演化使用

来源:互联网 发布:行知实践园自己感受 编辑:程序博客网 时间:2024/06/05 12:05
策略模式定义了方法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
还可以---那家咖啡屋,记忆里很温馨,很sugar,或许很多温馨美好的回忆都是于咖啡有关的。
我们常常说设计模式,因为这似乎就是高大上的好东西,如果不说就看不到我们自己的前途和上进心,我们今天就说说设计模式的经典—策略模式的故事。
每一个模式都会有自己的故事或者和其他模式有关系的故事甚至和框架的故事。在JAVA中策略模式举足轻重。原因很简单,JAVA语言是一门面向对象的编程语言,它强调使用多态来实现编程的灵活性和可维护性。在JAVA核心类库中到处都能看到策略模式的身影,例如Collections. sort(List<T> list, Comparator<? super T>@1 c),使用了Comparator比较器作为策略参数,我们可以选用一个特定的实现来对目标集合进行排序,而排序算法则有提供的Comparator的compare方法实现。这为排序算法提供了很高的自由度,严格来说是动态替换的选择性。
哦,这里有个小插曲,容我打断下,这个Comparator接口其实是个Functional接口,很明显Comparator只有一个compare方法@2,所以这也是一个lambda表达式可以使用的位置。
我在示例工程中也会演示使用lambda表达式实现策略模式的例子@3。现在我将通过具体的例子来继续和大家讨论。如果你困了建议你喝杯咖啡!
我的例子是建立在一个maven的工程中的,所以你最好会使用一点点maven,先把你的maven环境设置好,然后你也需要一个支持java8的eclipse,以便能导入示例工程@4。既然你已经准备好了,我们继续吧。
我通过父pom文件引入了junit和hamcrest测试包,还有guava(不用担心,它不过在这里例子中就是个玩具,你不用在意它)。这个项目目标是比较使用java的InputStream,BufferedInputStream,RandomAccessFile和FileChannel4种方法实现文件的sum和校验@5,
首先使用WithNoPolicyPatterns类通过定义多个static方法,形成方法族,这样就有了一个有4个“工具”方法的工具类,我之所以把这个4个方法(checksumWithInputStream,checksumWithBufferedInputStream,checksumWithRandomAccessFile,checksumWithFileChannel)定义为static方法是因为这个类具有称为工具类的“潜质”,它没有状态变量,所以不需要一个实例对状态进行封装,对方法如果不使用状态变量本身就是线程安全的(肯定不会逃逸)@6。我使用junit在test包中WithNoPolicyPatternsTest类对结果进行了测试,使用的测试目标文件是一个JRE目标下的jar文件。代码本身没有问题,他们的结果是相同的,可以将结果传递出去作为参数,问题是这些算法是独立的没有彼此的可替代性,这样将造成需要设计相关性的情况下,提供了无关性的支持。想一下,如果你使用spring这样的IOC框架,无法根据类型进行注入,那是很难受的,你就需要各种奇巧的方式来实现这种替换性,那显然就失去了面相对象提供的天然的组织能力。看来我们需要使用一下模式了。
这次我先新建一个PolicyInterface的接口,并添加一个long checksum(Path path) throws IOException的方法声明,这个方法要求子类都必须使用统一的方法名称checksum来实现校验和的计算。并且这个方法要求抛出IOException@7。然后我仍然使用相同的实现完成4个子类(CheckSumBufferedInputStreamPolicy,CheckSumInputStreamPolicy,CheckSumMappedFilePolicy,CheckSumRandomAccessFilePolicy),这样你就可以使用ctrl+T来查看继承树关系了。这似乎是一种设计实现带来的进步。然后使用WithPolicyPatternsTest类对策略模式进行测试。使用testChecksumMethods方法对这4个子类的动态替代性进行测试。
到这里我想看看这4种算法的执行效率,我们姑且不考虑虚拟机的诸如垃圾回收,hot代码等的影响。我们使用个时间计算的JDK动态代理来实现,
public class TimeElapsedHandler implements InvocationHandler {
private Object target;

public TimeElapsedHandler(Object t){
target=t;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Instant start=Instant.now();
method.invoke(target, args);
Instant end = Instant.now();
Duration elapsed=Duration.between(start, end);

return elapsed.toMillis();
}


}
这是样板代码,我将算法的执行结果替换为了执行时间@8。在测试类中使用testCheckSumMethodsElapsed对结果进行了测试。结果预期是bufferedInputStream和fileChannel要比inputStream和randomAccessFile效率更高。而bufferedInputStream和fileChannel,inputStream和randomAccessFile之间没有比较。
最后我们使用lambda表达式来实现这个策略模式的示例。代码写在CheckSumFunctions这个类中。
public static final ToLongFunction<Path> inputStream = (filename) -> {
try (InputStream in = Files.newInputStream(filename)) {
CRC32 crc = new CRC32();


int c;
while ((c = in.read()) != -1)
crc.update(c);
return crc.getValue();
} catch (IOException e) {
throw throwRuntimeException(e);
}


}
抽象类型是ToLongFunction,当然你也可以使用Function,因为这里并没有大量的boxed行为@9。这里有个小问题—4种算法的操作都会有IOException这个受检异常需要处理,而ToLongFunction的函数方法applyToLong并不会抛出这个异常,怎么办呢?所以需要捕获这个异常,并重新抛出@10。我们使用CheckSumFunctionsTest测试类对lambda化的策略模式进行活性测试和执行效率测试,并对异常转换进行了测试@11。
我今天和大家分享的策略模式和相关代码核心并不只是说明策略模式的使用和相关技术的,我更希望大家能在这个过程中发现一些新东西,埋藏在冰山下的宝藏。而不是之间树木不见森林。






注:@1上面关于使用泛型的部分我会在以后的博文中和大家一起讨论。
@2复写Object的方法,static方法,default方法不影响函数式接口的定义。
@3 策略模式有一个兄弟模式,叫做状态模式,关于这两个模式的区别于联系,大家可以在弄清楚本篇博文后,去查找相关的资料。
@4 工程代码地址http://download.csdn.net/download/jiawenhe123/10159155
@5你可以在JAVA核心技术卷2中找到相关的代码。
@6关于继承相关的话题(这里涉及到JAVA8引入default方法和static方法,导致很多人认为JAVA可以实现不使用内部类进行多继承的错觉)会在以后的相关博文中讨论。
@7JAVA要求子类不能比父类抛出更多的异常,所以你不用担心你有一个没有IO异常的算法不能实现这个接口。我相信很多人对于抛出异常,捕获异常根本没有深刻的概念,甚至没有耐心,这将严重损伤你的代码质量和影响你对代码库的理解。稍后我们将在使用lambda表达式实现策略模式的时候看到这个异常对代码的影响和处理方法。
@8代理不仅能改变行为的模式,而且能改变方法的作用类型,甚至可以和原来的操作完全不相关。只是我们通常不这么做。
@9lambda表达式有很多是为效率提供的接口。例如ToXxxFunction,XxxConsumer等。
@10受检异常通常是不可恢复的情况,所以在这里只捕获是没有价值的,需要抛出给调用的客户端,这里通过将IOException转换为RuntimeException,并将异常原因重定向后抛出。
@11你可以PackageTestSuite对所有测试类进行一次性测试。

原创粉丝点击