使用函数对象来定义运行策略

有些语言支持函数指针,代理(delegate),lambda表达式,或者其它类似的机制来存储和传递可执行的"函数"。这些特性允许程序在运行时动态改变行为,而这样的设计被称为"策略"模式(strategy pattern)。比如在c语言标准库中,qsort函数有一个参数是指向comparator函数的函数指针,这个comparator在排序过程中被用来对序列中的两个元素进行比较,通过传递不同的comparator函数,qsort可以动态采用不同的排序(比较)策略,而这个被传入的comparator就可以称为一个排序策略。

Java不支持函数指针,但通过对象引用可以实现类似的功能。通常说来,调用一个对象的方法,是要对这个对象本身进行操作。但如果一个方法接受的参数是指向其它对象的引用,那么这个方法也可以对这些传入的对象进行操作。如果一个类仅开放一个这样(对其他对象进行操作)的方法,那么它的对象就等同于一个指向该方法的函数指针,而这样的对象被称为函数对象。比如下面的类:

class StringLengthComparator {

	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}

显然这个类只提供一个依据字符串长度对它们进行比较的方法,指向此类对象的引用都可以被看作是一个函数指针,可以对任意两个字符串对象进行比较。换句话说,一个StringLengthComparator对象就是一个字符串比较的具体策略。

作为一个典型的策略类,StringLengthComparator类是无状态的:它没有任何属性,因此它的所有对象在功能上都是等价的。所以它应该被定义为一个Singleton以节省对象创建开销(item 3, item 5):

class StringLengthComparator {

	private StringLengthComparator() { }
	
	public static final StringLengthComparator
		INSTANCE = new StringLengthComparator();

	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}

为了把StringLengthComparator传递给一个方法,必须有一个参数类型。这时使用StringLengthComparator类本身显然是不合适的,那将使"策略"设计失去意义:除了StringLengthComparator以外,其它策略都无法被传递。为解决这个问题,可以定义一个Comparator接口并且修改StringLengthComparator,使其成为此接口的实现:

// Strategy interface
public interface Comparator<T> {

	public int compare(T t1, T t2);
}

class StringLengthComparator implements Comparator<String> {

	private StringLengthComparator() { }
	
	public static final StringLengthComparator
		INSTANCE = new StringLengthComparator();

	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}

具体策略类经常被声明为匿名类。比如下面的代码依据长度对字符串数组进行排序:

Arrays.sort(stringArray, new Comparator<String>() {
	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
});

需要注意,在这种用法中,每次执行上述代码都会有一个Comparator对象被创建。如果需要大量重复执行,应该把函数对象存储在一个static final属性中重复使用。

因为策略接口可以作为所有具体策略的类型,一个具体策略不必被声明为public类。现实中,只需要由一个"宿主类"开放以策略接口作为类型的public static属性,而把具体策略类定义为宿主的私有内嵌类。比如下面的例子:

// Exporting a concrete strategy
class Host {
	private static class StrLenCmp
			implements Comparator<String>, Serializable {
			
		public int compare(String s1, String s2) {
			return s1.length() - s2.length();
		}	
	}
	
	// Returned comparator is serializable
	public static final Comparator<String>
		STRING_LENGTH_COMPARATOR = new StrLenCmp();

	... // Bulk of class omitted
}

在标准库中,String类正是用这种方式,通过其CASE_INSENSITIVE_ORDER属性,开放了一个不区分大小写的comparator。

提交评论


安全码
刷新