×

消息

EU e-Privacy Directive

This website uses cookies to manage authentication, navigation, and other functions. By using our website, you agree that we can place these types of cookies on your device.

View e-Privacy Directive Documents

You have declined cookies. This decision can be reversed.

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

有些语言支持函数指针,代理(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。

提交评论


安全码
刷新