策略模式通常与使用标准模式的Java数据流(stream,Java 8之后有)或者Spark的RDD数据流配合使用,用于改变数据的处理策略,一般用在map和reduce操作。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。 4、Spark的mapToPair。
Java中的Map,有两种函数会用到策略模式,比如:computeIfAbsent(不存在则按照策略添加)、computeIfPresent(存在则按照策略更新),需要传入Function类,或者lambda表达式
内部代码实现是这样的:
1 @Override 2 public V computeIfAbsent(final K k, final Function<? super K, ? extends V> function) { 3 if (function == null) { 4 throw new NullPointerException(); 5 } 6 final int hash = hash(k); 7 int n = 0; 8 TreeNode<K, V> treeNode = null; 9 Node<K, V> treeNode2 = null; 10 Node<K, V>[] array; 11 int n2; 12 if (this.size > this.threshold || (array = this.table) == null || (n2 = array.length) == 0) { 13 n2 = (array = this.resize()).length; 14 } 15 final int n3; 16 final Node<K, V> node; 17 if ((node = array[n3 = (n2 - 1 & hash)]) != null) { 18 Label_0169: { 19 if (node instanceof TreeNode) { 20 treeNode2 = (treeNode = (TreeNode<K, V>)node).getTreeNode(hash, k); 21 } 22 else { 23 Node<K, V> next = node; 24 K key; 25 while (next.hash != hash || ((key = next.key) != k && (k == null || !k.equals(key)))) { 26 ++n; 27 if ((next = next.next) == null) { 28 break Label_0169; 29 } 30 } 31 treeNode2 = next; 32 } 33 } 34 final V value; 35 if (treeNode2 != null && (value = treeNode2.value) != null) { 36 this.afterNodeAccess(treeNode2); 37 return value; 38 } 39 } 40 final V apply = (V)function.apply(k); 41 if (apply == null) { 42 return null; 43 } 44 if (treeNode2 != null) { 45 treeNode2.value = apply; 46 this.afterNodeAccess(treeNode2); 47 return apply; 48 } 49 if (treeNode != null) { 50 treeNode.putTreeVal(this, array, hash, k, apply); 51 } 52 else { 53 array[n3] = this.newNode(hash, k, apply, node); 54 if (n >= 7) { 55 this.treeifyBin(array, hash); 56 } 57 } 58 ++this.modCount; 59 ++this.size; 60 this.afterNodeInsertion(true); 61 return apply; 62 } 63 64 @Override 65 public V computeIfPresent(final K k, final BiFunction<? super K, ? super V, ? extends V> biFunction) { 66 if (biFunction == null) { 67 throw new NullPointerException(); 68 } 69 final int hash = hash(k); 70 final Node<K, V> node; 71 final V value; 72 if ((node = this.getNode(hash, k)) != null && (value = node.value) != null) { 73 final V apply = (V)biFunction.apply(k, value); 74 if (apply != null) { 75 node.value = apply; 76 this.afterNodeAccess(node); 77 return apply; 78 } 79 this.removeNode(hash, k, null, false, true); 80 } 81 return null; 82 }HashMap中用策略模式的函数
可以看到,传入的function对象在某些执行路线下,会被执行apply()以使用里面的策略。
外部可以这样调用:
myMap.computeIfAbsent(keyC, k -> k + "2");//如果keyC没有对应值,则把值设为keyC和"2"这个字符串的连接
myMap.computeIfPresent(keyC, (v1, v2) -> v1 + "," + v2);//如果keyC有对应值,则把值设为原值和逗号和插入值这个字符串的连接
这样,Map就无需为这些策略继承过多的子类,减少了无效的开发量。
如果需要用默认function,其实Java内部也有一些内部实现。
优点: 1、算法可以*切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。(用语法糖,比如lambda就可以解决) 2、所有策略类都需要对外暴露。(这个其实问题不大,控制一下开发流程和查看权限就可以了)
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。