程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> EffectiveJava——用函數對象表示策略

EffectiveJava——用函數對象表示策略

編輯:JAVA編程入門知識

  有些語言支持函數指針、代理、lambda表達式,或者支持類似的機制,允許程序把“調用特殊函數的能力”儲存起來並傳遞這種能力。這種機制通常用於允許函數的調用者通過傳入第二個函數,來指定自己的行為。比較器函數有兩個參數,都是指向元素的指針。如果第一個參數所指的元素小於第二個參數所指的元素,則返回一個負整數;如果兩個元素相等則返回零;如果第一個參數所指的元素大雨第二個,則返回一個正整數。通過傳遞不同的比較器函數,就可以獲得各種不同的排列順序。這正是策略模式的一個例子。比較器函數代表一種為元素排列的策略。

  Java沒有提供函數指針,但是可以用對象引用實現同樣的功能。調用對象上的方法通常是執行該對象上的某個操作。然而,我們也可能定義這樣一種對象,它的方法執行其他對象上的操作。如果一個類僅僅導出這樣的一個方法,它的實例上就等同於一個指向該方法的指針。這樣的實例被稱為函數對象。考慮這樣一個類:

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

  這個類導出一個帶兩個字符串參數的方法。指向StringLengthComparator對象的引用可以被當做是一個指向該比較器的“函數指針”,可以在任意一對字符串上被調用。換句話說,StringLengthComparator實例是用於字符串比較操作的具體策略。

  作為典型的具體策略類,StringLengthComparator類是無狀態的:它沒有域,所以,這個類的所有實例在功能上是相互等價的。因此,它作為一個Singleton是非常合適的,可以節省不必要的對象創建開銷:

/**
 * 用函數對象表示策略
 * @author weishiyao
 *
 */
public 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並不好,因為客戶端無法傳遞任何其他的比較策略。相反,我們需要定義一個Comparator接口,並修改StringLengthComparator來實現這個接口。換句話說,我們在設計具體的策略類時,還需要定義一個策略接口:

// Strategy interface
public interface Comparator<T> {
	public int compare(T t1, T t2);
}

  Comparator接口的這個定義碰巧也出現在java.util包中,但是這並不神奇,我們自己也可以定義它。Comparator接口時范型的,因此它適合作為除字符串之外其他對象的比較器。它的compare兩個參數類型為T,而不是String。只要聲明前面所示的StringLengthComparator類要這麼做,就可以用它實現Comparator<String>接口。

  具體的策略類往往使用匿名類聲明,下面的語句根據長度對一個字符串數組進行排序:

		Arrays.sort(stringArray, new Comparator<T>() {
			@Override
			public int compare(String s1, String s2) {
				// TODO Auto-generated method stub
				return s1.length() - s2.length();
			}
		});

  但是注意,以這種方式使用匿名類時,將會在每次執行調用的時候創建一個新的實例。如果它被重復執行,考慮將函數對象存儲到一個私有的靜態final域裡,並重用它。這樣做的另一種好處是,可以為這個函數對象取一個有意義的域名。

  因為策略接口被用作所有具體策略實例的類型,所以並不需要為了倒出具體策略,而把策略類做成公有的。相反“宿主類”還可以導出公有的靜態域,其類型為策略接口,具體的策略類可以是宿主類的私有前套類。下面的例子使用靜態成員類,而不是匿名類,以便允許具體的策略類實現第二個接口Serializable

/**
 * 用函數對象表示策略
 * @author weishiyao
 *
 */
public class Host {
	private static class StrLenCmp implements Comparator<String>, Serializable {
		/**
		 * 
		 */
		private static final long serialVersionUID = -5797980299250787300L;

		public int compare(String s1, String s2) {
			return s1.length() - s2.length();
		}
	}
	
	// Return comparator is serializable
	public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
	
	public static void main(String[] args) {
		System.out.println(STRING_LENGTH_COMPARATOR.compare("aaaaaa", "aaaaa"));
	}
}

  String類利用這種模式,通過它的STRING_LENGTH_COMPARATOR域,導出一個不區分大小寫的字符串比較器。

  總而言之,函數指針的主要用途就是實現策略模式。為了在java中實現這種模式,要聲明一個接口來表示該策略,並且為每一個策略聲明一個實現了該接口的類。當一個具體策略只被使用一次時,通常使用匿名類來聲明和實例化這個具體策略類。當一個具體策略類時設計用來重復使用的時候,它的類通常就要被實現為私有的靜態成員類,並通過公有的靜態final域被導出,其類型為該策略接口。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved