设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
已写过的设计模式
单例模式:设计模式-单例模式
工厂模式:设计模式-工厂模式
模板方法、建造者、代理模式:设计模式-模板方法模式、建造者模式、代理模式及动态代理的两种实现方式
今天要说的设计模式有两种:状态模式和策略模式
设计模式-状态模式![de67a2d9ea5d3ceeaf47531567b49e52.png](https://i-blog.csdnimg.cn/blog_migrate/8e6ce5dfdd50fe8c64cc4ff873ea20c8.png)
在状态模式中,类的行为由状态决定;允许对象在内部状态变更时,改变其行为方式
实现要素一个抽象状态类
(1)多个抽象方法,表示状态下的行为
(2)一个对象引用,用于操作对象行为
多个子类继承抽象状态类,即多种状态
子类个性化实现父类的抽象方法,不同方法在不同的状态下实现效果不同
一个角色类,即行为随状态变更的对象类
(1)一个状态引用,用于在行为操作时对状态的变更
!!!状态类中持有角色对象引用,用于操作对象行为
角色类中持有状态对象引用,用于变更对象状态
UML类图定义一个抽象状态类-电梯运行状态
package com.lee.state.demo01;/** * 抽象电梯状态类 * */public abstract class LiftState { /** * 定义一个环境角色,也就是封装状态的变化引起的功能变化 */ protected Context context; public void setContext(Context context) { this.context = context; } /** * 电梯开门动作 */ public abstract void open(); /** * 电梯关门动作 */ public abstract void close(); /** * 电梯运行动作 */ public abstract void run(); /** * 电梯停止动作 */ public abstract void stop();}
定义多个状态实现类
开门状态
package com.lee.state.demo01;public class OpenningState extends LiftState{ /** * 电梯处于开门状态时,再次开启将不做任何动作 */ @Override public void open() { System.out.println("电梯门开启....."); } /** * 电梯处于开门状态,关门操作将会修改电梯状态 */ @Override public void close() { //状态修改 context.setLiftState(Context.closingState); //动作委托给CloseState来执行 context.getLiftState().close(); } /** * 电梯处于开门状态,运行操作将不会做任何动作 */ @Override public void run() { } /** * 电梯处于开门状态,停止操作将不会做任何动作 */ @Override public void stop() { }}
关门状态:
package com.lee.state.demo01;public class ClosingState extends LiftState{ /** * 电梯处于关闭状态时,再次开启将修改状态 */ @Override public void open() { context.setLiftState(Context.openningState); context.getLiftState().open(); } /** * 电梯处于关闭状态,关门操作将不会做任何操作 */ @Override public void close() { System.out.println("电梯门关闭...."); } /** * 电梯处于关闭状态,运行操作修改状态 */ @Override public void run() { context.setLiftState(Context.runningState); context.getLiftState().run(); } /** * 电梯处于关闭状态,停止操作将会修改状态 */ @Override public void stop() { context.setLiftState(Context.stoppingState); context.getLiftState().stop(); }}
运行状态:
package com.lee.state.demo01;public class RunningState extends LiftState{ /** * 电梯处于运行状态时,再次开启将不做任何动作 */ @Override public void open() { } /** * 电梯处于运行状态,关门操作将会修改电梯状态 */ @Override public void close() { } /** * 电梯处于运行状态,运行操作将不会做任何动作 */ @Override public void run() { System.out.println("电梯正在运行....."); } /** * 电梯处于运行状态,停止操作将会修改状态 */ @Override public void stop() { context.setLiftState(Context.stoppingState); context.getLiftState().stop(); }}
停止状态:
package com.lee.state.demo01;public class StoppingState extends LiftState{ /** * 电梯处于停止状态时,开启操作将会修改状态 */ @Override public void open() { context.setLiftState(Context.openningState); context.getLiftState().open(); } /** * 电梯处于停止状态,关门操作将不会做任何动作 */ @Override public void close() { } /** * 电梯处于停止状态,运行操作将会修改状态 */ @Override public void run() { context.setLiftState(Context.runningState); context.getLiftState().run(); } /** * 电梯处于停止状态,停止操作将不会做任何动作 */ @Override public void stop() { System.out.println("电梯正处于停止状态......"); }}
定义一个角色类
package com.lee.state.demo01;/** * 上下文类 */public class Context { /** * 定义出所有的电梯状态 */ public final static OpenningState openningState = new OpenningState(); public final static ClosingState closingState = new ClosingState(); public final static RunningState runningState = new RunningState(); public final static StoppingState stoppingState = new StoppingState(); /** * 定义一个当前电梯状态 */ private LiftState liftState; public LiftState getLiftState() { return liftState; } public void setLiftState(LiftState liftState) { this.liftState = liftState; //此处必须将操作的上下文传过去,就像坐电梯时,人不见了,是不是很恐怖 this.liftState.setContext(this); } /** * 将开门动作委托给context执行 */ public void open(){ this.liftState.open(); } /** * 将关门动作委托给context执行 */ public void close(){ this.liftState.close(); } /** * 将运行动作委托给context执行 */ public void run(){ this.liftState.run(); } /** * 将停止动作委托给context执行 */ public void stop(){ this.liftState.stop(); }}
测试Demo
package com.lee.state.demo01;public class TestDemo { public static void main(String[] args) { Context context = new Context(); context.setLiftState(Context.closingState); context.open(); context.close(); context.run(); context.stop(); }}
测试结果
1、资源状态变更:设计-入网-退网-报废
2、订单状态变更:未支付-已支付-已退款
3、条件、分支语句的优化
设计模式-策略模式![de67a2d9ea5d3ceeaf47531567b49e52.png](https://i-blog.csdnimg.cn/blog_migrate/8e6ce5dfdd50fe8c64cc4ff873ea20c8.png)
类的行为或者算法可以自由切换,最终都为实现同一目的,可以理解为--殊途同归
实现要素一个抽象策略接口
(1)一个或多个策略行为方法
多个策略实现类,实现抽象策略接口
(1)策略行为方法由实现类个性化实现,但是同一类策略的目的需一致
一个角色类,用于接收各种策略
这里我采用一个排序算法的选择来实现策略模式
定义一个抽象策略类或接口
package com.lee.strategy.demo01;public interface SortStrategy { int[] sort(int[] arr);}
冒泡排序算法
package com.lee.strategy.demo01.bubble;import com.lee.strategy.demo01.SortStrategy;/** * 冒泡排序 */public class BubbleSort implements SortStrategy { public int[] sort(int[] arr){ int temp = 0; boolean flag = false; for (int i = 0;i1;i++){ //因为每进行一趟排序,都会确定一个值到最后,所以确定好的值就不用再进行排序了 for(int j = 0;j1-i;j++){ if(arr[j]>arr[j+1]){ flag = true; temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } if(!flag){ break; }else{ flag = false; } } return arr; }}
插入排序算法
package com.lee.strategy.demo01.insert;import com.lee.strategy.demo01.SortStrategy;/** * 插入排序,类比打扑克中的起牌环节 */public class InsertSort implements SortStrategy { public int[] sort(int[] arr) { //默认第一个元素是有序的,从第二个元素开始排序 for (int i = 1; i < arr.length; i++) { //待排序的数 int insertVal = arr[i]; //想要插入的位置 int insertIndex = i - 1; //需要判断防止一直往前移位导致数组越界 //如果待插入的值 while (insertIndex >= 0 && insertVal < arr[insertIndex]) { arr[insertIndex + 1] = arr[insertIndex]; //插入排序是从当前相邻的位置开始动手,如果当前相邻位置比较不成功,就往前再移一位,前面的都是已经排过序的 insertIndex--; } //当跳出循环时,说明待排序的数已经找到了合适的位置 //因为上面执行了--操作,此时需要将index的值加回来 arr[insertIndex+1] = insertVal; } return arr; }}
归并排序算法
package com.lee.strategy.demo01.merge;import com.lee.strategy.demo01.SortStrategy;/** * 归并排序 */public class MergeSort implements SortStrategy { public int[] sort(int[] arr) { int temp[] = new int[arr.length]; sort(arr,0,arr.length - 1, temp); return arr; } public void sort(int[] arr, int left, int right, int[] temp) { if (left < right) { int mid = (left + right) / 2; //向左递归分解 sort(arr, left, mid, temp); //向右递归分解 sort(arr, mid + 1, right, temp); //最后一定要合并 merge(arr, left,right,mid , temp); } } public static void merge(int[] arr, int left, int right, int mid, int[] temp) { //初始化i:表示左边有序序列的初始索引 int i = left; //初始化j:表示右边有序序列的初始索引 int j = mid + 1; //初始化t:表示临时数组的当前索引 int t = 0; //第一步:先将左右两边(有序)的数据按照规则(左取一个,右取一个,相互比较,小的存入临时数组中) //填充到临时数组temp中,直到左右两边的有序序列,有一边处理完毕为止 while (i <= mid && j <= right) { //当左取元素小于右取元素时,将左取元素放置到临时数组中,然后左侧索引往右移动一位,临时数组的索引往右移动一位 if (arr[i] < arr[j]) { temp[t] = arr[i]; i += 1; t += 1; } //当右取元素小于左取元素时,将右取元素放置到临时数组中,然后右侧索引往右移动一位,临时数组的索引往右移动一位 else { temp[t] = arr[j]; j += 1; t += 1; } } //第二步:当左侧还有剩余元素时,需要将左侧元素依次移动到临时数组中 //判断依据:如果左侧已经取完了,那么左侧的索引i应该已经超过了mid while (i <= mid) { temp[t] = arr[i]; i += 1; t += 1; } //第三步:当右侧还有剩余元素时,需要将右侧元素依次移动到临时数组中 //判断依据:如果右侧已经取完了,那么右侧的索引i应该已经超过了right while (j <= right) { temp[t] = arr[j]; j += 1; t += 1; }// System.out.println(Arrays.toString(temp)); //第四步:将临时数组中的数据移动到原数组中 t = 0; int tempLeft = left; while (tempLeft <= right) { arr[tempLeft] = temp[t]; t += 1; tempLeft += 1; } }}
选择排序算法
package com.lee.strategy.demo01.select;import com.lee.strategy.demo01.SortStrategy;/** * 选择排序 * 选择一个值作为假定最小值,然后分别去比较,如果有更小的,就替换假定最小值 */public class SelectSort implements SortStrategy { public int[] sort(int[] arr) { for (int i = 0; i < arr.length; i++) { //假设第i个值为最小值 int minValue = arr[i]; int minIndex = i; //第一个值arr[0]已经取出,从第二个值开始比较 for (int j = 1; j < arr.length; j++) { if(minIndex > arr[j]){//说明假定最小值,并不是最小 minValue = arr[j]; minIndex = j; } } //每一轮会获取一个最小值,将最小值放到对应的位置 if(minIndex!=0){ arr[minIndex] = arr[i]; //因为是按顺序取得值,所以在放置值时也是按照1、2、3...有序放置 arr[i] = minValue; } } return arr; }}
希尔排序算法
package com.lee.strategy.demo01.shell;import com.lee.strategy.demo01.SortStrategy;/** * 希尔排序 */public class ShellSort implements SortStrategy { public int[] sort(int[] arr){ //先根据数组的length除以2,得到一个分组,这个分组的个数其实也是步长 //一轮之后再进行除2,缩小步长 int temp = 0; for (int gap = arr.length/2; gap > 0; gap /=2) { for (int i = 0; i < arr.length && i+gap if(arr[i]>arr[i+gap]){ temp = arr[i+gap]; arr[i+gap] = arr[i]; arr[i] = temp; } } } return arr; }}
定义一个角色类
package com.lee.strategy.demo01;public class Context { /** * 抽象算法策略 */ private SortStrategy sortStrategy; public Context(SortStrategy sortStrategy) { this.sortStrategy = sortStrategy; } public void sort(int[] arr){ this.sortStrategy.sort(arr); }}
同样80000个数据,测试排序效率
测试结果
1、计算规则选择
2、支付方式选择
3、条件、分支语句优化
状态模式与策略模式区别
![de67a2d9ea5d3ceeaf47531567b49e52.png](https://i-blog.csdnimg.cn/blog_migrate/8e6ce5dfdd50fe8c64cc4ff873ea20c8.png)
状态模式:重点在于状态的切换,由状态的切换衍生行为的区别,状态之间有一定的关系
策略模式:重点在于策略的选择,同一策略族中每次只能选择一种策略,各个策略之间无影响
行为作用状态模式:不同的状态下行为不同,产生的结果也不同
比如同一个人,小时候不能谈恋爱,长大后可以谈恋爱
策略模式:策略实现不同,但都是为了同一个目的,即殊途同归
比如同一个人长大后可以谈恋爱了,恋爱对象可以是任何人(切勿想歪),恋爱对象对于这个人就是不同的策略
模式封装状态模式:主要封装的是状态,状态与对象是强相关的,状态决定了对象的行为,状态不能复用
比如A的年龄是3岁,可以做的事是和尿泥
B的年龄是30岁,可以做的事是还房贷
A的年龄不可能让B复用,B也不可能去和尿泥,因为他要努力敲代码还贷
策略模式:主要封装的是算法或行为,这是可以替换的
比如A有个女友B,某天因为不可抗因素,换了一个女朋友C,实际结果同样都是交女朋友
实现细节状态模式:
状态类中持有角色类的引用,用于操作角色的行为
角色类中持有状态类的引用,用于切换状态
角色的的行为就是切换状态
策略模式:
策略只能给角色使用,不能持有角色引用
外界影响状态模式:
状态模式依赖于其对象内部状态变化来决定行为,将动作委托到代表当前状态的对象,对外表现为类发生了变化,状态转移及行为有内部封装,外界调用者无法对其进行修改,相当于是自动的
策略模式:
策略模式的客户端需要对所需要的策略特别了解,才能在合适的场景下使用合适的策略,策略是外界给的,策略的行为由调用者决定,和角色无关,相当于是手动的