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(){@Overridepublic 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{@Overrideprotected void brew() {System.out.println("now add coffee...");}@Overrideprotected void addCondiments() {System.out.println("now coffee add milk and sugar...");}
}class HotTea extends absHotDrinks{@Overrideprotected void brew() {System.out.println("now add tea...");}@Overrideprotected 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 1sttreeifyBin(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 keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e); //看这里-----------return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict); //看这里-----------return null;}@Overridepublic 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;elsep.next = node.next;++modCount;--size;afterNodeRemoval(node); //看这里---------------return node;}}return null;}// Callbacks to allow LinkedHashMap post-actionsvoid 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 lastLinkedHashMap.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;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;}}void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.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 lastLinkedHashMap.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;elseb.after = a;if (a != null)a.before = b;elselast = 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 1sttreeifyBin(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,已存在映射的keyV 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 eldestLinkedHashMap.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里面的模板方法模式体现在哪里?(其实是一个问题,大家可以选择自行去学习一下)