1.模板方法模式的概念
介紹:
模板方法模式是編程中經常用得到模式。它定義了ー個操作中的算法骨架,將某些步驟延遲到子類中實現。這樣,新的子類可以在不改變個算法結構的前提下重新定義該算法的某些特定步驟(這是一種設計思路,一定要細細品味)。
核心:
處理某個流程的代碼已經都具備,但是其中某個節點的代碼暫時不能確定。因此,我們採用工廠方法模式,將這個節點的代碼實現轉移給子類完成。即:處理步驟父類中定義好,具體實現延退到子類中定義
2.模板方法模式的結構
參與者:
AbstractClass(抽象類,如Application)
定義抽象的原語操作(primitive operation),具體的子類將重定義它們以實現一個算法的各步驟。
實現一個模板方法 ,定義一個算法的骨架。該模板方法不僅調用原語操作,也調用定義 在AbstractClass或其他對象中的操作。ConcreteClass(具體類,如MyApplication)
實現原語操作以完成算法中與特定子類相關的步驟。
下面看一個簡單的例子:
public class TemplateMethodPattern {
public static void main(String[] args) {
HotCoffee coffee = new HotCoffee();
coffee.getHotDrinks();
System.out.println();
HotTea tea1 = new HotTea(){
@Override
public boolean customerNeedCondiments() {
return false;
}
};
tea1.getHotDrinks();
System.out.println();
HotTea tea2 = new HotTea();
tea2.getHotDrinks();
System.out.println();
}
}
//Abstract算法抽象類
abstract class absHotDrinks {
//燒水
private void boilWater() {
System.out.println("boilWater()...");
}
//衝調料
protected abstract void brew();
//倒入被子
private void putInCup() {
System.out.println("putInCup()...");
}
//加調料
protected abstract void addCondiments();
//鉤子,控制父類,用戶是否要加調料
public boolean customerNeedCondiments() {
return true;
}
//具體算法放放,防止子類改變算法步驟,使用final進行修飾,子類不能重寫
public final void getHotDrinks() {
boilWater();
brew();
putInCup();
if (customerNeedCondiments()) {
addCondiments();
}
}
}
class HotCoffee extends absHotDrinks{
@Override
protected void brew() {
System.out.println("now add coffee...");
}
@Override
protected void addCondiments() {
System.out.println("now coffee add milk and sugar...");
}
}
class HotTea extends absHotDrinks{
@Override
protected void brew() {
System.out.println("now add tea...");
}
@Override
protected void addCondiments() {
System.out.println("now tea add sugar...");
}
}
/* 輸出結果
Hello World!
boilWater()...
now add coffee...
putInCup()...
now coffee add milk and sugar...
boilWater()...
now add tea...
putInCup()...
boilWater()...
now add tea...
putInCup()...
now tea add sugar...
* */
上面這個簡單的例子使用了模板方法設計模式,但是不容易被理解(至少對於我來說是這樣的,就是這些簡單的例子讓我產生了懷疑,這模板方式到底有啥用?這種設計模式是在逗我吧!),所以具體還是需要看一下在JDK和各大框架中使用到模板方法的代碼,看看這個大神是如何做的。
在JDK中實現了模板方法設計模式的例子有很多,比如AbstractList、HashMap、AQS,再比如JDK8中各個集合類接口中的default方法,都使用到了模板方法設計模式。
首先看一下JDK8中各個集合類接口中的default方法是如何使用模板方法設計模式的。
1.首先看一下JDK8中Map接口使用模板方法設計模式:
public interface Map<K,V> {
//省略了其他不重要的方法
V remove(Object key);
V get(Object key);
V put(K key, V value);
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
}
在Map接口中,沒有定義get、put和remove的具體操作,其具體操作由子類來實現,而對於putIdAbsent、getOrDefault等方式需要使用到get、put和remove方法,這就是典型的模板方法設計模式,對於putIdAbsent、getOrDefault等default方法是在JDK8中加入的方法,可以提供更爲實用的方法,而在子類中不需要任何改動就可以實現。
2.HashMap中所使用到的模板方法設計模式
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); //看這裏-----------
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict); //看這裏-----------
return null;
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e); //看這裏--------------
return true;
}
return false;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node); //看這裏---------------
return node;
}
}
return null;
}
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
在HashMap中很多方法都會調用afterNodeAccess、afterNodeInsertion、afterNodeRemoval方法,而這些方法HashMap本身並沒有實現,對於這些函數HashMap的子類,LinkedHashMap實現了這些方法,看如下代碼:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
/*
*省略其他的方法,僅展示關鍵代碼
*/
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
}
afterNodeAccess方法:在訪問一個新節點後的操作(在LinkedHashMap中,put一個key值已存在的,也算是訪問)
put方法使用final修飾,子類無法實現這個方法,這和模板方法設計模式一樣的,然後提供afterNodeInsertion和afterNodeAccess讓子類實現,對於
對HashMap中的put方法片段進行分析,此時的e代表插入一個元素時,Map中是否已有key值相同的元素,如果存在的話,說明只是修改key對應的value,這時可以執行afterNodeAccess供子類進行操作,對於LinkedHashMap的afterNodeAccess是根據插入有序還有訪問有序,將該節點放到隊列的首部(LinkedHashMap裏面有鏈表,可以記錄訪問或查詢的順序,使用LinkedHashMap可以很輕鬆的實現LRU算法,其關鍵就是這三個方法)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key,已存在映射的key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); //執行完這句就返回了
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict); //如果key不存在的話,就執行這個
return null;
}
afterNodeInsertion方法:在插入一個新節點後的操作
插入新節點的意思是Map的Size增加了,比如
Map<String,String> map = new HashMap<>(); map.put("123","123"); 新的節點,在put時會執行afterNodeInsertion方法 map.put("123","12345"); Map的Size增加沒有增加,在put時會執行afterNodeAccess方法 map.put("321","123"); 新的節點,在put時會執行afterNodeInsertion方法
afterNodeInsertion是插入一個新節點的操作(再次強調是Map的Size增加),看一下LinkedHashMap的afterNodeInsertion方法,目的的判斷在插入一個節點就需要不需要刪除鏈表的末尾的操作(這個方法是實現LRU算法的關鍵,因爲LRU算法會將最不常用的那個數進行移除掉,對於要實現LRU算法,我們可以實現LinkedHashMap,然後重寫removeEldestEntry方法就可以了,這裏又是一個模板方法模式,對於LinkedHashMap不懂的可以自行百度)。
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { //如果要實現LRU算法子類可以重寫這個方法,就可以輕鬆實現
return false;
}
afterNodeRemoval方法:在移除一個節點的操作
該方法不做過多介紹了
這裏拋出兩個問題,沒事的時候可以搜一下:
使用LinkedHashMap如何實現LRU算法?
LinkedHashMap裏面的模板方法模式體現在哪裏?(其實是一個問題,大家可以選擇自行去學習一下)