Strategy 策略,在编程里可以理解为算法。在策略模式中一个类的行为或其算法可以在运行时更改。在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。
策略模式 Strategy Design Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.——GoF 定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。
所谓客户端 Client 指的就是使用到算法的地方。
🍐⚱️:里面就是组合加委托。
登场角色:
Strategy(策略):决定实现策略所必须的接口(API)。 ConcreteStrategy(具体的策略):负责实现 Strategy 角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法) Context(上下文/环境):负责使用 Strategy 角色。它保存了 ConcreteStrategy 角色的实例,并使用 ConcreteStrategy 角色去实现需求(调用 Strategy 角色的接口(API))。Context 类可以通过 站内文章 依赖注入 、站内文章 简单工厂 等方式来获取具体策略对象。
使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void main (String[] args) throws Exception { Strategy strategy = null ; Properties props = new Properties (); props.load(new FileInputStream ("./config.properties" )); String type = props.getProperty("strategy_type" ); strategy = StrategyFactory.getStrategy(type); UserCache userCache = new UserCache (strategy); } public static void main (String[] args) { Strategy strategy = new LruEvictionStrategy (); UserCache userCache = new UserCache (strategy); }
特意编写 Strategy 角色目的就是将算法与其他部分分离开来,只定义了与算法相关的接口(API),然后在程序中以委托的方式来使用算法。使用委托这种弱关联关系可以很方便地整体替换算法。
我们可以在程序中切换 ConcreteStrategy 角色。比如在内存少的时候用速度慢但省内存的策略。或者可以用某种算法去「验算」另一种算法。
如果我们使用策略工厂创建策略对象,当需要增加新的策略时,为了避免代码修改,可以对工厂进行改造:通过加载配置文件或搜索使用自定义注解标注的策略类,通过 站内文章 反射 动态加载策略类并创建策略对象。
优点 缺点 可以在运行时切换对象内的算法。 如果你的算法极少发生改变,那么没有任何理由引入新的类和接口。使用该模式只会让程序过于复杂。 可以将算法的实现和使用算法的代码隔离开来。 客户端必须知晓策略间的不同——它需要选择合适的策略。 可以使用组合来代替继承。 许多现代编程语言支持函数类型功能,允许你在一组匿名函数中实现不同版本的算法。这样,你使用这些函数的方式就和使用策略对象时完全相同,无需借助额外的类和接口来保持代码简洁。 开闭原则。无需对上下文进行修改就能够引入新的策略。
相关的设计模式:
站内文章 享元模式 :有时会使用享元模式让多个地方可以共用 ConcreteStrategy 角色。 站内文章 抽象工厂 : 使用策略模式可以整体地替换算法 。 使用抽象工厂模式则可以整体地替换具体工厂、零件和产品 。 站内文章 状态模式 :使用策略模式和状态模式都可以替换被委托对象,而且它们的类之间的关系也很相似。但是两种模式的目的不同。 在策略模式中,ConcreteStrategy 角色是表示算法的类 。在策略模式中,可以替换被委托对象的类 。当然如果没有必要,也可以不替换 。 而在状态模式中,ConcreteState 角色是表示「状态」的类 。在状态模式中,每次状态变化时,被委托对象的类都必定会被替换。 站内文章 模板方法模式 : 模板方法模式基于继承机制:它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制:你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作,因此它是静态的。策略在对象层次上运作,因此允许在运行时切换行为。 不同模式中解耦的对象:
工厂模式:解耦对象的创建和使用 观察者模式:解耦观察者和被观察者 策略模式:解耦策略的定义、创建和使用 策略模式和「多态」感觉很像,但这是两个东西。策略模式是一种代码编写模式,侧重于应用场景;多态是一种技术,侧重于代码实现。
策略模式优化代码示例 背景:编写对文件进行排序的程序,电脑内存只有 8G。文件中只包含整型数,并且,相邻的数字通过逗号来区隔。
当文件小于 6GB 时,我们可以将文件读到内存使用快速排序算法排好序,再写入到文件中。 当文件大于 6GB 且小于 10GB 时,使用外部排序算法进行排序。 当文件大于 10GB 小于 100GB 时,利用 CPU 多核优势,在外部排序的基础上使用多线程进行优化。 当文件大于 100GB 时,使用 MapReduce 框架,利用多机的处理能力,提高排序的效率。 一个基础的实现方式 算法骨架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Sorter { private static final long GB = 1000 * 1000 * 1000 ; public void sortFile (String filePath) { File file = new File (filePath); long fileSize = file.length(); if (fileSize < 6 * GB) { quickSort(filePath); } else if (fileSize < 10 * GB) { externalSort(filePath); } else if (fileSize < 100 * GB) { concurrentExternalSort(filePath); } else { mapreduceSort(filePath); } } private void quickSort (String filePath) {} private void externalSort (String filePath) {} private void concurrentExternalSort (String filePath) {} private void mapreduceSort (String filePath) {} }
使用方式:
1 2 3 4 5 6 public class SortingTool { public static void main (String[] args) { Sorter sorter = new Sorter (); sorter.sortFile(args[0 ]); } }
存在问题:多种算法实现集中在 Sorter 类中,文件过大,影响到可读性、可维护性。排序算法为私有函数,影响可复用性。
策略的抽取 下面将算法抽取为类,将其与业务逻辑解耦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public interface ISortAlg { void sort (String filePath) ; } public class QuickSort implements ISortAlg { @Override public void sort (String filePath) {} } public class Sorter { private static final long GB = 1000 * 1000 * 1000 ; public void sortFile (String filePath) { File file = new File (filePath); long fileSize = file.length(); ISortAlg sortAlg; if (fileSize < 6 * GB) { sortAlg = new QuickSort (); } else if (fileSize < 10 * GB) { sortAlg = new ExternalSort (); } else if (fileSize < 100 * GB) { sortAlg = new ConcurrentExternalSort (); } else { sortAlg = new MapReduceSort (); } sortAlg.sort(filePath); } }
观察上面代码我们可以发现,每种策略都是无状态的,我们没必要在每次使用的时候重新创建一个新的对象。
使用工厂模式优化策略类的创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class SortAlgFactory { private static final Map<String, ISortAlg> algs = new HashMap <>(); static { algs.put("QuickSort" , new QuickSort ()); algs.put("ExternalSort" , new ExternalSort ()); algs.put("ConcurrentExternalSort" , new ConcurrentExternalSort ()); algs.put("MapReduceSort" , new MapReduceSort ()); } public static ISortAlg getSortAlg (String type) { if (type == null || type.isEmpty()) { throw new IllegalArgumentException ("type should not be empty." ); } return algs.get(type); } } public class Sorter { private static final long GB = 1000 * 1000 * 1000 ; public void sortFile (String filePath) { File file = new File (filePath); long fileSize = file.length(); ISortAlg sortAlg; if (fileSize < 6 * GB) { sortAlg = SortAlgFactory.getSortAlg("QuickSort" ); } else if (fileSize < 10 * GB) { sortAlg = SortAlgFactory.getSortAlg("ExternalSort" ); } else if (fileSize < 100 * GB) { sortAlg = SortAlgFactory.getSortAlg("ConcurrentExternalSort" ); } else { sortAlg = SortAlgFactory.getSortAlg("MapReduceSort" ); } sortAlg.sort(filePath); } }
目前的代码已经符合策略模式的结构了,策略模式将策略的定义、创建和使用解耦。
使用查表逻辑优化 if-else 代码 上面的代码中策略创建时存在 if-else 逻辑,如果你的 if-else 逻辑过于复杂的话,考虑使用查表逻辑优化创建过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Sorter { private static final long GB = 1000 * 1000 * 1000 ; private static final List<AlgRange> algs = new ArrayList <>(); static { algs.add(new AlgRange (0 , 6 *GB, SortAlgFactory.getSortAlg("QuickSort" ))); algs.add(new AlgRange (6 *GB, 10 *GB, SortAlgFactory.getSortAlg("ExternalSort" ))); algs.add(new AlgRange (10 *GB, 100 *GB, SortAlgFactory.getSortAlg("ConcurrentExternalSort" ))); algs.add(new AlgRange (100 *GB, Long.MAX_VALUE, SortAlgFactory.getSortAlg("MapReduceSort" ))); } public void sortFile (String filePath) { File file = new File (filePath); long fileSize = file.length(); ISortAlg sortAlg = null ; for (AlgRange algRange : algs) { if (algRange.inRange(fileSize)) { sortAlg = algRange.getAlg(); break ; } } sortAlg.sort(filePath); } private static class AlgRange { private long start; private long end; private ISortAlg alg; public AlgRange (long start, long end, ISortAlg alg) { this .start = start; this .end = end; this .alg = alg; } public ISortAlg getAlg () { return alg; } public boolean inRange (long size) { return size >= start && size < end; } } }
文本绘图代码备份 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Context{ strategy {method} contextMethod } abstract class Strategy{ {method} {abstract} strategyMethod } class ConcreteStrategy1{ {method} strategyMethod1 } class ConcreteStrategy2{ {method} strategyMethod2 } Context o-> Strategy Strategy <|-- ConcreteStrategy1 Strategy <|-- ConcreteStrategy2
本文参考