6,行为型模式 行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为:
模板方法模式
策略模式
命令模式
职责链模式
状态模式
观察者模式
中介者模式
迭代器模式
访问者模式
备忘录模式
解释器模式
以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
6.1 模板方法模式 6.1.1 概述 在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
6.1.2 结构 模板方法(Template Method)模式包含以下主要角色:
6.1.3 案例实现 【例】炒菜
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
代码如下:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public abstract class AbstractClass { public final void cookProcess () { this .pourOil(); this .heatOil(); this .pourVegetable(); this .pourSauce(); this .fry(); } public void pourOil () { System.out.println("倒油" ); } public void heatOil () { System.out.println("热油" ); } public abstract void pourVegetable () ; public abstract void pourSauce () ; public void fry () { System.out.println("炒啊炒啊炒到熟啊" ); } } public class ConcreteClass_BaoCai extends AbstractClass { @Override public void pourVegetable () { System.out.println("下锅的蔬菜是包菜" ); } @Override public void pourSauce () { System.out.println("下锅的酱料是辣椒" ); } } public class ConcreteClass_CaiXin extends AbstractClass { @Override public void pourVegetable () { System.out.println("下锅的蔬菜是菜心" ); } @Override public void pourSauce () { System.out.println("下锅的酱料是蒜蓉" ); } } public class Client { public static void main (String[] args) { ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai (); baoCai.cookProcess(); ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin (); caiXin.cookProcess(); } }
注意:为防止恶意操作,一般模板方法都加上 final 关键词。
6.1.3 优缺点 优点:
缺点:
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
6.1.4 适用场景
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
6.1.5 JDK源码解析 InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:
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 public abstract class InputStream implements Closeable { public abstract int read () throws IOException; public int read (byte b[]) throws IOException { return read(b, 0 , b.length); } public int read (byte b[], int off, int len) throws IOException { if (b == null ) { throw new NullPointerException (); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException (); } else if (len == 0 ) { return 0 ; } int c = read(); if (c == -1 ) { return -1 ; } b[off] = (byte )c; int i = 1 ; try { for (; i < len ; i++) { c = read(); if (c == -1 ) { break ; } b[off + i] = (byte )c; } } catch (IOException ee) { } return i; } }
从上面代码可以看到,无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。
在该方法中第18行、27行,可以看到调用了无参的抽象的 read() 方法。
总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。
6.2 策略模式 6.2.1 概述 先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。
作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
6.2.2 结构 策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
6.2.3 案例实现 【例】促销活动
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
代码如下:
定义百货公司所有促销活动的共同接口
1 2 3 public interface Strategy { void show () ; }
定义具体策略角色(Concrete Strategy):每个节日具体的促销活动
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 StrategyA implements Strategy { public void show () { System.out.println("买一送一" ); } } public class StrategyB implements Strategy { public void show () { System.out.println("满200元减50元" ); } } public class StrategyC implements Strategy { public void show () { System.out.println("满1000元加一元换购任意200元以下商品" ); } }
定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
1 2 3 4 5 6 7 8 9 10 11 12 13 public class SalesMan { private Strategy strategy; public SalesMan (Strategy strategy) { this .strategy = strategy; } public void salesManShow () { strategy.show(); } }
6.2.4 优缺点 1,优点:
策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。
易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
2,缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
6.2.5 使用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
6.2.6 JDK源码解析 Comparator 中的策略模式。在Arrays类中有一个 sort() 方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class Arrays { public static <T> void sort (T[] a, Comparator<? super T> c) { if (c == null ) { sort(a); } else { if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); else TimSort.sort(a, 0 , a.length, c, null , 0 , 0 ); } } }
Arrays就是一个环境角色类,这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。就比如下面的测试类。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class demo { public static void main (String[] args) { Integer[] data = {12 , 2 , 3 , 2 , 4 , 5 , 1 }; Arrays.sort(data, new Comparator <Integer>() { public int compare (Integer o1, Integer o2) { return o2 - o1; } }); System.out.println(Arrays.toString(data)); } }
这里我们在调用Arrays的sort方法时,第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays类的sort方法到底有没有使用Comparator子实现类中的 compare() 方法吗?让我们继续查看TimSort类的 sort() 方法,代码如下:
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 class TimSort <T> { static <T> void sort (T[] a, int lo, int hi, Comparator<? super T> c, T[] work, int workBase, int workLen) { assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length; int nRemaining = hi - lo; if (nRemaining < 2 ) return ; if (nRemaining < MIN_MERGE) { int initRunLen = countRunAndMakeAscending(a, lo, hi, c); binarySort(a, lo, hi, lo + initRunLen, c); return ; } ... } private static <T> int countRunAndMakeAscending (T[] a, int lo, int hi,Comparator<? super T> c) { assert lo < hi; int runHi = lo + 1 ; if (runHi == hi) return 1 ; if (c.compare(a[runHi++], a[lo]) < 0 ) { while (runHi < hi && c.compare(a[runHi], a[runHi - 1 ]) < 0 ) runHi++; reverseRange(a, lo, runHi); } else { while (runHi < hi && c.compare(a[runHi], a[runHi - 1 ]) >= 0 ) runHi++; } return runHi - lo; } }
上面的代码中最终会跑到 countRunAndMakeAscending() 这个方法中。我们可以看见,只用了compare方法,所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行,这也是Comparator接口中必须要子类实现的一个方法。
6.3 命令模式 6.3.1 概述 日常生活中,我们出去吃饭都会遇到下面的场景。
定义:
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
6.3.2 结构 命令模式包含以下主要角色:
抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
6.3.3 案例实现 将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。
服务员: 就是调用者角色,由她来发起命令。
资深大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。
类图如下:
代码如下:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 public interface Command { void execute () ; } public class OrderCommand implements Command { private SeniorChef receiver; private Order order; public OrderCommand (SeniorChef receiver, Order order) { this .receiver = receiver; this .order = order; } public void execute () { System.out.println(order.getDiningTable() + "桌的订单:" ); Set<String> keys = order.getFoodDic().keySet(); for (String key : keys) { receiver.makeFood(order.getFoodDic().get(key),key); } try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(order.getDiningTable() + "桌的饭弄好了" ); } } public class Order { private int diningTable; private Map<String, Integer> foodDic = new HashMap <String, Integer>(); public int getDiningTable () { return diningTable; } public void setDiningTable (int diningTable) { this .diningTable = diningTable; } public Map<String, Integer> getFoodDic () { return foodDic; } public void setFoodDic (String name, int num) { foodDic.put(name,num); } } public class SeniorChef { public void makeFood (int num,String foodName) { System.out.println(num + "份" + foodName); } } public class Waitor { private ArrayList<Command> commands; public Waitor () { commands = new ArrayList (); } public void setCommand (Command cmd) { commands.add(cmd); } public void orderUp () { System.out.println("美女服务员:叮咚,大厨,新订单来了......." ); for (int i = 0 ; i < commands.size(); i++) { Command cmd = commands.get(i); if (cmd != null ) { cmd.execute(); } } } } public class Client { public static void main (String[] args) { Order order1 = new Order (); order1.setDiningTable(1 ); order1.getFoodDic().put("西红柿鸡蛋面" ,1 ); order1.getFoodDic().put("小杯可乐" ,2 ); Order order2 = new Order (); order2.setDiningTable(3 ); order2.getFoodDic().put("尖椒肉丝盖饭" ,1 ); order2.getFoodDic().put("小杯雪碧" ,1 ); SeniorChef receiver=new SeniorChef (); OrderCommand cmd1 = new OrderCommand (receiver, order1); OrderCommand cmd2 = new OrderCommand (receiver, order2); Waitor invoker = new Waitor (); invoker.setCommand(cmd1); invoker.setCommand(cmd2); invoker.orderUp(); } }
6.3.4 优缺点 1,优点:
降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
2,缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。
系统结构更加复杂。
6.3.5 使用场景
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
系统需要在不同的时间指定请求、将请求排队和执行请求。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
6.3.6 JDK源码解析 Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法
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 Runnable { public abstract void run () ; } public class Thread implements Runnable { private Runnable target; public synchronized void start () { if (threadStatus != 0 ) throw new IllegalThreadStateException (); group.add(this ); boolean started = false ; try { start0(); started = true ; } finally { try { if (!started) { group.threadStartFailed(this ); } } catch (Throwable ignore) { } } } private native void start0 () ; }
会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class TurnOffThread implements Runnable { private Receiver receiver; public TurnOffThread (Receiver receiver) { this .receiver = receiver; } public void run () { receiver.turnOFF(); } }
1 2 3 4 5 6 7 8 9 10 11 public class Demo { public static void main (String[] args) { Receiver receiver = new Receiver (); TurnOffThread turnOffThread = new TurnOffThread (receiver); Thread thread = new Thread (turnOffThread); thread.start(); } }
6.4 责任链模式 6.4.1 概述 在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。
定义:
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
6.4.2 结构 职责链模式主要包含以下角色:
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
6.4.3 案例实现 现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。
类图如下:
代码如下:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 public class LeaveRequest { private String name; private int num; private String content; public LeaveRequest (String name, int num, String content) { this .name = name; this .num = num; this .content = content; } public String getName () { return name; } public int getNum () { return num; } public String getContent () { return content; } } public abstract class Handler { protected final static int NUM_ONE = 1 ; protected final static int NUM_THREE = 3 ; protected final static int NUM_SEVEN = 7 ; private int numStart; private int numEnd; private Handler nextHandler; public Handler (int numStart) { this .numStart = numStart; } public Handler (int numStart, int numEnd) { this .numStart = numStart; this .numEnd = numEnd; } public void setNextHandler (Handler nextHandler) { this .nextHandler = nextHandler; } public final void submit (LeaveRequest leave) { if (0 == this .numStart){ return ; } if (leave.getNum() >= this .numStart){ this .handleLeave(leave); if (null != this .nextHandler && leave.getNum() > numEnd){ this .nextHandler.submit(leave); } else { System.out.println("流程结束" ); } } } protected abstract void handleLeave (LeaveRequest leave) ; } public class GroupLeader extends Handler { public GroupLeader () { super (Handler.NUM_ONE, Handler.NUM_THREE); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("小组长审批:同意。" ); } } public class Manager extends Handler { public Manager () { super (Handler.NUM_THREE, Handler.NUM_SEVEN); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("部门经理审批:同意。" ); } } public class GeneralManager extends Handler { public GeneralManager () { super (Handler.NUM_SEVEN); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("总经理审批:同意。" ); } } public class Client { public static void main (String[] args) { LeaveRequest leave = new LeaveRequest ("小花" ,5 ,"身体不适" ); GroupLeader groupLeader = new GroupLeader (); Manager manager = new Manager (); GeneralManager generalManager = new GeneralManager (); groupLeader.setNextHandler(manager); manager.setNextHandler(generalManager); groupLeader.submit(leave); } }
6.4.4 优缺点 1,优点:
降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。
增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
2,缺点:
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
6.4.5 源码解析 在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析:
6.5 状态模式 6.5.1 概述 【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。
类图如下:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 public interface ILift { public final static int OPENING_STATE = 1 ; public final static int CLOSING_STATE = 2 ; public final static int RUNNING_STATE = 3 ; public final static int STOPPING_STATE = 4 ; public void setState (int state) ; public void open () ; public void close () ; public void run () ; public void stop () ; } public class Lift implements ILift { private int state; @Override public void setState (int state) { this .state = state; } @Override public void close () { switch (this .state) { case OPENING_STATE: System.out.println("电梯关门了。。。" ); this .setState(CLOSING_STATE); break ; case CLOSING_STATE: break ; case RUNNING_STATE: break ; case STOPPING_STATE: break ; } } @Override public void open () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯门打开了。。。" ); this .setState(OPENING_STATE); break ; case RUNNING_STATE: break ; case STOPPING_STATE: System.out.println("电梯门开了。。。" ); this .setState(OPENING_STATE); break ; } } @Override public void run () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯开始运行了。。。" ); this .setState(RUNNING_STATE); break ; case RUNNING_STATE: break ; case STOPPING_STATE: System.out.println("电梯开始运行了。。。" ); this .setState(RUNNING_STATE); break ; } } @Override public void stop () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯停止了。。。" ); this .setState(STOPPING_STATE); break ; case RUNNING_STATE: System.out.println("电梯停止了。。。" ); this .setState(STOPPING_STATE); break ; case STOPPING_STATE: break ; } } } public class Client { public static void main (String[] args) { Lift lift = new Lift (); lift.setState(ILift.STOPPING_STATE); lift.open(); lift.close(); lift.run(); lift.stop(); } }
问题分析:
使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。
扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑
定义:
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
6.5.2 结构 状态模式包含以下主要角色。
环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为。
6.5.3 案例实现 对上述电梯的案例使用状态模式进行改进。类图如下:
代码如下:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 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 () ; } public class OpenningState extends LiftState { @Override public void open () { System.out.println("电梯门开启..." ); } @Override public void close () { super .context.setLiftState(Context.closeingState); super .context.getLiftState().close(); } @Override public void run () { } @Override public void stop () { } } public class RunningState extends LiftState { @Override public void open () { } @Override public void close () { } @Override public void run () { System.out.println("电梯正在运行..." ); } @Override public void stop () { super .context.setLiftState(Context.stoppingState); super .context.stop(); } } public class StoppingState extends LiftState { @Override public void open () { super .context.setLiftState(Context.openningState); super .context.getLiftState().open(); } @Override public void close () { super .context.setLiftState(Context.closeingState); super .context.getLiftState().close(); } @Override public void run () { super .context.setLiftState(Context.runningState); super .context.getLiftState().run(); } @Override public void stop () { System.out.println("电梯停止了..." ); } } public class ClosingState extends LiftState { @Override public void close () { System.out.println("电梯门关闭..." ); } @Override public void open () { super .context.setLiftState(Context.openningState); super .context.open(); } @Override public void run () { super .context.setLiftState(Context.runningState); super .context.run(); } @Override public void stop () { super .context.setLiftState(Context.stoppingState); super .context.stop(); } } public class Context { public final static OpenningState openningState = new OpenningState (); public final static ClosingState closeingState = new ClosingState (); public final static RunningState runningState = new RunningState (); public final static StoppingState stoppingState = new StoppingState (); private LiftState liftState; public LiftState getLiftState () { return this .liftState; } public void setLiftState (LiftState liftState) { this .liftState = liftState; this .liftState.setContext(this ); } public void open () { this .liftState.open(); } public void close () { this .liftState.close(); } public void run () { this .liftState.run(); } public void stop () { this .liftState.stop(); } } public class Client { public static void main (String[] args) { Context context = new Context (); context.setLiftState(new ClosingState ()); context.open(); context.close(); context.run(); context.stop(); } }
6.5.4 优缺点 1,优点:
将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
2,缺点:
状态模式的使用必然会增加系统类和对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
状态模式对”开闭原则”的支持并不太好。
6.5.5 使用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
6.6 观察者模式 6.6.1 概述 定义:
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
6.6.2 结构 在观察者模式中有如下角色:
Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
6.6.3 案例实现 【例】微信公众号
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
类图如下:
代码如下:
定义抽象观察者类,里面定义一个更新的方法
1 2 3 public interface Observer { void update (String message) ; }
定义具体观察者类,微信用户是观察者,里面实现了更新的方法
1 2 3 4 5 6 7 8 9 10 11 12 public class WeixinUser implements Observer { private String name; public WeixinUser (String name) { this .name = name; } @Override public void update (String message) { System.out.println(name + "-" + message); } }
定义抽象主题类,提供了attach、detach、notify三个方法
1 2 3 4 5 6 7 8 9 10 11 public interface Subject { public void attach (Observer observer) ; public void detach (Observer observer) ; public void notify (String message) ; }
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class SubscriptionSubject implements Subject { private List<Observer> weixinUserlist = new ArrayList <Observer>(); @Override public void attach (Observer observer) { weixinUserlist.add(observer); } @Override public void detach (Observer observer) { weixinUserlist.remove(observer); } @Override public void notify (String message) { for (Observer observer : weixinUserlist) { observer.update(message); } } }
客户端程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Client { public static void main (String[] args) { SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject (); WeixinUser user1=new WeixinUser ("孙悟空" ); WeixinUser user2=new WeixinUser ("猪悟能" ); WeixinUser user3=new WeixinUser ("沙悟净" ); mSubscriptionSubject.attach(user1); mSubscriptionSubject.attach(user2); mSubscriptionSubject.attach(user3); mSubscriptionSubject.notify("传智黑马的专栏更新了" ); } }
6.6.4 优缺点 1,优点:
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】
2,缺点:
如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
6.6.5 使用场景
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。
6.6.6 JDK中提供的实现 在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1,Observable类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
2,Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
【例】警察抓小偷
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:
小偷是一个被观察者,所以需要继承Observable类
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 Thief extends Observable { private String name; public Thief (String name) { this .name = name; } public void setName (String name) { this .name = name; } public String getName () { return name; } public void steal () { System.out.println("小偷:我偷东西了,有没有人来抓我!!!" ); super .setChanged(); super .notifyObservers(); } }
警察是一个观察者,所以需要让其实现Observer接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Policemen implements Observer { private String name; public Policemen (String name) { this .name = name; } public void setName (String name) { this .name = name; } public String getName () { return name; } @Override public void update (Observable o, Object arg) { System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!" ); } }
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 public class Client { public static void main (String[] args) { Thief t = new Thief ("隔壁老王" ); Policemen p = new Policemen ("小李" ); t.addObserver(p); t.steal(); } }