page 654
Exercise 4: (2) Use Directory.walk( ) to sum the sizes of all files in a directory tree whose names match a particular regular expression.
import java.util.regex.*;
import java.io.*;
import java.util.*;
public final class Directory {
public static int size = 0;// added
public static File[] local(File dir, final String regex) {
return dir.listFiles(new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return pattern.matcher(new File(name).getName()).matches();
}
});
}
public static File[] local(String path, final String regex) {
return local(new File(path), regex);
}
public static class TreeInfo implements Iterable<File> {
public List<File> files = new ArrayList<File>();
public List<File> dirs = new ArrayList<File>();
public Iterator<File> iterator() {
return files.iterator();
}
void addAll(TreeInfo other) {
files.addAll(other.files);
dirs.addAll(other.dirs);
}
public String toString() {
return "dirs: " + PPrint.pformat(dirs) + "/n/nfiles: "
+ PPrint.pformat(files);
}
}
public static TreeInfo walk(String start, String regex) {
return recurseDirs(new File(start), regex);
}
public static TreeInfo walk(File start, String regex) {
return recurseDirs(start, regex);
}
public static TreeInfo walk(File start) {
return recurseDirs(start, ".*");
}
public static TreeInfo walk(String start) {
return recurseDirs(new File(start), ".*");
}
static TreeInfo recurseDirs(File startDir, String regex) {
TreeInfo result = new TreeInfo();
for (File item : startDir.listFiles()) {
if (item.isDirectory()) {
result.dirs.add(item);
result.addAll(recurseDirs(item, regex));
} else if (item.getName().matches(regex)) {
result.files.add(item); //
try {
InputStream temp = new FileInputStream(item);
size += temp.available();
temp.close();
} catch (IOException e) {
System.out.print("File can not be read!");
} //
}
}
return result;
}
public static void main(String[] args) {
if (args.length == 0) {
System.out.println(walk("."));
System.out.print("size: " + size + " bytes");
} else
for (String arg : args) {
System.out.println(walk(arg));
System.out.println("size: " + size + " bytes");
size = 0;
}
}
}
上面练习题要求改写Thinking in Java书中代码,改写难度不是很大,关键是在学习书中代码的结构.如果不了解Strategy这种设计模式,Exercise 5这段代码就很晦涩难懂了。其实还不如把process()函数写成abtract,然后新建类来实现这个函数达到运行时多态,设计模式大多的用意就是解耦合,这个当然也不例外。Strategy也有很多种实现形式,这段代码确实是最漂亮的。
Strategy两大原则
设计模式真正体现的是Java的原则,而这些原则又通过这些优秀的模式反映出来。有些面向对象的原则,适用于所有的模式,当无法找到适当的模式解决问题时,这些原则就是唯一的标准了。Strategy模式体现了如下的两大原则:
针对接口编程,而不是针对实现编程。
多用组合,少用继承。
Strategy策略模式又名政策(Policy)模式,是属于设计模式中对象行为型模式,主要是定义一系列的算法,把这些算法一个个封装成单独的类,并且这些类(算法)可以相互替换。Strategy应用比较广泛,比如, 公司经营业务变化图, 可能有两种实现方式,一个是线条曲线,一个是框图(bar),这是两种算法,可以使用Strategy实现。图象压缩系列算法实现(不同的算法分别对应一个类)也可以采用Strategy模式实现。
下面我将从五个方面详细介绍该模式。
1. 意图与动机
当有一系统算法时,在算法的选择上,如果用if…else…则是非常不明智的,把这些算法抽象出来,然后根据程序的需要,动态地加载,这便是策略模式。定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
2. 结构
Strategy的结构如下图所示:
下面简要介绍一下以上类图中各个类的作用:
1) Context类为上下文类,负责维护和更新算法所要处理的环境和数据。譬如对于图象压缩来说,Context负责数据的提取、压缩参数的获得以及压缩以后善后工作等等;
2) Strategy代表抽象的算法,譬如对于图象压缩来说,它代表的就是图象压缩算法,至于具体那种压缩?JEPG压缩还是GIF压缩,Strategy类不关心,它只为具体的图象压缩算法提供统一的接口;
3) ConcreteStrategyA、ConcreteStrategyB、ConcreteStrategyC则代表特别具体的算法。
Context类维护Strategy的一个引用(或者指针)。一旦Context所维护的上下文有变化(譬如输入数据有变化),它就将这个职责转发给它的Strategy对象。Context的客户制定应该使用哪一种Strategy(算法)的方式是直接将它想要的具体的Strategy (ConcreteStrategyA、ConcreteStrategyB、ConcreteStrategyC……)装入Composition中。
3. 实用性
当存在一下情况时,适合使用Strategy模式
1) 许多相关的类仅仅是行为上的差异。“策略”提供了一种用多个行为中的一个行为类配置一个类的方法;
2) 需要使用一个算法的不同的变体。例如,您可能会定义一些反映不同空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式;
3) 算法使用客户不应该知道的具体的实现步骤,可以使用策略模式以免暴露复杂的、与算法相关的数据结构;
4) 一个类定义了多种行为,并且为这些行为在这个类的操作中以多个条件语句的形式出现相关的条件分支移入它们各自的Strategy类中,以代替这些语句。
5. 效果
Strategy模式具有以下优点:
1) 相关算法系列:Strategy类层次为Context定义了一系列的可重用的算法或者行为。继承有助于析取算法中的公共功能,例如参数合法性验证,可以放在Strategy类层次;
2) 消除一些条件语句:Strategy模式提供了用体检语句选择所需的行为以为的另外一种选择。当不同的行为堆砌在一个类中,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句;
3) 选择合适的实现:Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间/空间权衡取舍要求不同策略中进行选择。
然而世界上没有完美的事情,Strategy模式在带来方便的同时,也存在如下的一些缺点:
1) 客户必须了解不同的Strategy:这是本模式潜在的一个缺点,其实客户要选择一个合适的Strategy就必须知道这些Strategy有何不同。这不可避免地向客户暴露具体的实现问题。
2) Strategy和Context之间的通信开销:无论各个ConcreteStrategy实现算法是简单还是复杂,它们都共享Strategy定义的接口。因此,很有可能具体的ConcreteStrategy(有些ConcreteStrategy可能非常简单,不需要太多的参数)不会用到通过这个接口传递的某些信息,这样就会造成信息的浪费;
3) 增加了对象的数目:Strategy增加了一个应用中的对象的数目(譬如有多个上下文的时候,每个上下文中都存在Strategy对象)。有时候你可以将Strategy实现为可供各个Context共享的无状态的对象来减少这以开销。