行为型模式
概述:
行为型模式用于描述程序在运行时复杂的流程控制,简单来说,行为型模式就是用于描述多个类或对象之间如何相互协作来共同完成单个对象都无法完成的任务,它涉及算法和对象间的职责分配问题。
分类:(11种,除了标注的模式外,其余都为对象行为模式)
- 模板方法模式(类行为模式)
- 策略模式
- 命令模式
- 职责链模式
- 状态模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式(类行为模式)
1 模板方法模式
1.1 定义
定义一个操作中的算法骨架,而将算法中的一些步骤延迟到子类中,使得子类可以在不改变算法结构的前提下,重新定义该算法的某些特定步骤。
1.2 结构
抽象类:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
*模板方法:定义了算法的骨架,一般用final修饰,按某种顺序调用其包含的基本方法。
*基本方法:是实现算法中各个步骤的方法,是模板方法的组成部分。基本方法又分为三种:
- 抽象方法:由抽象类声明,但由其子类具体实现。
- 具体方法:由抽象类或具体类声明并实现,其子类可以继承也可以重写。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
- 具体子类:实现了抽象类中所有的抽象方法和钩子方法。
1.3 优缺点
优点:
*提高代码复用
将相同的代码放入抽象的父类中,而将不同的代码放入子类中。
*实现了反向控制
通过一个父类调用子类的操作,通过对子类的具体实现进行扩展,实现了“反向控制”,并符合“开闭原则”。
缺点:
*对每个不同的实现都需要定义一个子类,会导致类的数量增加,系统更加庞大,设计也更加抽象。
*父类中的抽象方法由子类实现,子类的执行结果会影响父类的执行结果,这导致一种反向的控制结构,增加了阅读代码的难度。
1.4 适用场景
*算法的整体步骤相对稳定,但其中的个别部分容易发生改变时,可以使用模板方法模式,将容易改变的部分抽象出来,让子类实现。
*需要子类来决定父类中的某个步骤是否执行,实现子类对父类的反向控制。
1.5 JDK源码解析
例如:InputStream类就使用了模板方法模式,定义了多个read()方法(重载)。
1.6 【案例实现】炒菜
炒菜的步骤是固定的,一般分为5步:倒油,热油,倒蔬菜,倒调料,翻炒。将通过模板方法模式来实现。类图如下:
Dishes类:
1 package my_test; 2 /** 3 * 抽象类 炒菜 4 * @author Asus 5 *包含一个模板方法(算法)和一些基本方法 6 */ 7 public abstract class Dishes { 8 // 模板方法(炒菜的顺序) 9 public final void cook(){ 10 dao(); 11 hot(); 12 put(); 13 spice(); 14 fry(); 15 16 } 17 18 // 具体方法:倒油 19 public void dao() { 20 System.out.println("开始往锅里倒油啦..."); 21 } 22 // 具体方法:热油 23 public void hot() { 24 System.out.println("锅里的油热啦..."); 25 } 26 // 抽象方法:倒菜 27 abstract void put(); 28 // 抽象方法:倒调料 29 abstract void spice(); 30 // 具体方法:翻炒 31 public void fry() { 32 System.out.println("炒呀炒炒呀炒..."); 33 } 34 }
Mapo_tofu 类:
1 package my_test; 2 /** 3 * 炒菜的实现子类 菜品一 麻婆豆腐 4 * @author Asus 5 * 6 */ 7 public class Mapo_tofu extends Dishes{ 8 // 倒菜 9 @Override 10 void put() { 11 System.out.println("开始往锅里放豆腐、牛肉末啦..."); 12 } 13 // 放调料 14 @Override 15 void spice() { 16 System.out.println("放点调料酱油、盐、豆瓣酱会更美味哦..."); 17 } 18 19 }
Sweet_and_sour_fish类:
1 package my_test; 2 /** 3 * 炒菜的实现子类 菜品一 糖醋鱼 4 * @author Asus 5 * 6 */ 7 public class Sweet_and_sour_fish extends Dishes { 8 // 倒菜 9 @Override 10 void put() { 11 System.out.println("开始往锅里放鱼、葱花啦..."); 12 } 13 // 放调料 14 @Override 15 void spice() { 16 System.out.println("放点调料番茄酱、醋会更美味哦..."); 17 } 18 }
测试类:Test_Dishes
1 package my_test; 2 /** 3 * 炒菜的测试类 4 * @author Asus 5 * 6 */ 7 public class Test_Dishes { 8 9 public static void main(String[] args) { 10 // 炒麻婆豆腐 11 System.out.println("-----*炒麻婆豆腐*-----------"); 12 Mapo_tofu mt=new Mapo_tofu(); 13 mt.cook(); 14 System.out.println(); 15 16 // 炒糖醋鱼 17 System.out.println("------*炒糖醋鱼*----------"); 18 Sweet_and_sour_fish sf = new Sweet_and_sour_fish(); 19 sf.cook(); 20 21 } 22 23 }
运行结果:
2 策略模式
2.1 定义
该模式定义了一系列算法,并把每个算法封装起来,这些算法可以相互替换,且算法的改变不会影响到使用算法的客户。
策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和实现向隔离,并委派给不同的对象对这些算法进行管理。
2.2 结构
- 抽象策略类:这是一个抽象角色,通常由一个接口或抽象类实现,来给出所有具体策略类所需的接口(即方法)。
- 具体策略类:实现了抽象策略类定义的接口,提供具体的算法实现或行为。
- 环境类:持有一个策略类的引用,最终给客户端调用。
2.3 优缺点:
*优点:
- 策略类之间可以相互转换:由于策略类共同实现同一个接口,所以使它们之间可以自己转换。
- 便于扩展:增加一个新策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则”。
- 避免使用多重条件选择语句(if else):充分体现面向对象设计思想。
*缺点:
- 客户端必须知道所有的策略类,并决定使用哪一个具体的策略类。
- 将产生很多策略类的对象,可以通过享元模式在一定程度上减少对象的数量。
2.4 使用场景
- 一个系统需要动态地选择其中一种算法时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式实现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 要求各算法彼此完全独立,且要求对客户端隐藏内部具体算法的实现细节时。
- 要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行是动态选择具体要执行的行为。
2.5 JDK源码解析
Comparator中的策略模式。在Arrays(环境类)类中有一个sort()方法。
2.6 【案例实现】 促销活动
一家百货公司在年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户,类图如下:
Startegy类:
1 package my_test2; 2 /** 3 * 抽象类 活动 4 * @author Asus 5 * 6 */ 7 public abstract class Startegy { 8 // 活动内容 9 abstract void show(); 10 }
StartegyA类:
1 package my_test2; 2 /** 3 * 抽象类的实现一 买一送一活动 4 * @author Asus 5 * 6 */ 7 public class StartegyA extends Startegy { 8 // 买一送一活动 9 @Override 10 void show() { 11 System.out.println("买一送一活动"); 12 13 } 14 15 }
StartegyB类:
1 package my_test2; 2 /** 3 * 抽象类的实现二 满120元送25元活动 4 * @author Asus 5 * 6 */ 7 public class StartegyB extends Startegy{ 8 9 @Override 10 void show() { 11 System.out.println("满120元送25元活动"); 12 } 13 14 }
StartegyC类:
1 package my_test2; 2 /** 3 * 抽象类的实现三 打八五折活动 4 * @author Asus 5 * 6 */ 7 public class StartegyC extends Startegy { 8 9 @Override 10 void show() { 11 System.out.println("打八五折活动"); 12 13 } 14 15 }
SalesMan类:
1 package my_test2; 2 /** 3 * 环境类 促销员 4 * @author Asus 5 * 6 */ 7 public class SalesMan { 8 public Startegy startegy; //活动类实例 9 // 构造方法 10 public SalesMan(Startegy startegy) { 11 this.startegy=startegy; 12 } 13 // 显示促销活动 14 public void salesManShow() { 15 startegy.show(); 16 } 17 18 public void setStartegy(Startegy startegy) { 19 this.startegy = startegy; 20 } 21 22 }
测试类:TestSales
1 package my_test2; 2 3 public class TestSales { 4 5 public static void main(String[] args) { 6 // 节日:春节运行结果 活动:买一送一 7 SalesMan sm=new SalesMan(new StartegyA()); 8 sm.salesManShow(); 9 10 System.out.println("现将活动调整为:"); 11 // 节日:春节 活动:打八五折 12 sm.setStartegy(new StartegyC()); 13 sm.salesManShow(); 14 } 15 16 }
运行结果:
3 命令模式
3.1 定义
将一个请求封装为一个对象,使发出的请求和执行请求分隔开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
3.2 结构
- 抽象命令角色:定义命令的接口、声明执行的方法。
- 具体命令角色:具体的命令,实现命令接口;通常持有接受者,并调用接受者的功能来完成命令要执行的操作。
- 调用者角色:要求命令对象执行请求,通常持有命令对象,命令对象的个数可以为多个。这个是客户端真正触发命令,并要求命令执行响应操作的地方,也就是想当于使用命令对象的入口。
- 接受者角色:接收者,真正执行命令的对象。任何类都有可能成为接收者,只要它能够实现命令要求实现的相应功能。
3.3 优缺点:
*优点
- 降低耦合度:能将调用操作的对象与实现该操作的对象解耦。
- 便于扩展:增加或删除命令非常方便,采用命令模式增加与删除命令不会影响其他类,满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令:命令模式可以与组合模式相结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现撤销和恢复操作:命令模式可以与备忘录模式相结合,实现命令的撤销和恢复。
*缺点
- 可能导致某些系统有过多的具体命令类。
- 使结构更加复杂。
3.4 使用场景
- 需要将请求调用者和请求接受者进行解耦,使调用者和接受者不直接交互时,使用命令模式。
- 需要在不同的时间指定请求,将请求排队和执行请求时,使用命令模式。
- 需要支持命令的撤销操作和恢复操作时,使用命令模式。
3.5 JDK源码解析
Runnable是一个典型的命令模式,Runnable担当命令角色,Thread充当的是调用者,start方法就是执行方法。
3.6 【案例实现】 点餐
顾客吧订单交给服务员,服务员拿着订单放在订单柜台,然后喊了一声“订单来啦!”,厨师根据订单准备餐点。
服务员:调用者角色,由服务员来发起命令。
厨师:接受者角色,真正执行命令的对象。
订单:命令中包含订单。
类图如下:
Waiter类:
1 package my_test3; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * 请求者角色(服务员) 8 * @author Asus 9 * 10 */ 11 public class Waiter { 12 // 持有多个命令对象 13 private List<Command> commands=new ArrayList<Command>(); 14 15 // 将cmd对象存储到List集合中 16 public void setCommand(Command cmd){ 17 commands.add(cmd); 18 } 19 20 // 发起命令,服务员喊一声"订单来啦" 21 public void orderUp() { 22 System.out.println("服务员说:厨师,新订单来啦"); 23 // 遍历List集合 24 for(Command cmd:commands) { 25 if(cmd!=null) { 26 cmd.execute(); 27 } 28 } 29 30 } 31 }
SeniorChef类:
1 package my_test3; 2 /** 3 * 厨师类 4 * @author Asus 5 * 6 */ 7 public class SeniorChef { 8 public void makefood(String name,int num) { 9 System.out.println(name+"->"+num+"份"); 10 } 11 }
OrderCommand 类:
1 package my_test3; 2 3 import java.util.Map; 4 import java.util.Set; 5 6 /** 7 * 具体命令 8 * @author Asus 9 * 10 */ 11 public class OrderCommand implements Command{ 12 // 持有接收者对象 13 private SeniorChef receiver; 14 private Order order; 15 16 public OrderCommand(SeniorChef receiver, Order order) { 17 this.receiver = receiver; 18 this.order = order; 19 } 20 21 @Override 22 public void execute() { 23 System.out.println("-------*"+order.getTableNum()+"桌的订单*-----------"); 24 Map<String,Integer> foodDir=order.getFoodDir(); 25 // 遍历Map集合 26 foodDir.forEach((k,v)->System.out.println(k+" "+v+"份")); 27 System.out.println(order.getTableNum()+"桌的饭,已经做好啦!"); 28 } 29 30 }
Order类:
1 package my_test3; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 /** 7 * 订单类 8 * @author Asus 9 * 10 */ 11 public class Order { 12 // 餐桌号码 13 private int tableNum; 14 // 所点的餐品及份数 15 private Map<String,Integer> foodDir=new HashMap<String,Integer>(); 16 17 18 // 返回点餐的餐桌号 19 public int getTableNum() { 20 return tableNum; 21 } 22 // 设置点餐的餐桌号 23 public void setTableNum(int tableNum) { 24 this.tableNum = tableNum; 25 } 26 //返回点的菜品及份数 27 public Map<String, Integer> getFoodDir() { 28 return foodDir; 29 } 30 // 设置点的菜品及份数 31 public void setFood(String name,int num) { 32 foodDir.put(name, num); 33 } 34 35 }
Command接口:
1 package my_test3; 2 /** 3 * 抽象命令类 4 * @author Asus 5 * 6 */ 7 public interface Command { 8 // 执行命令 9 void execute(); 10 11 }
Client类:
1 package my_test3; 2 /** 3 * 测试类 订餐 4 * @author Asus 5 * 6 */ 7 public class Client { 8 public static void main(String[] args) { 9 System.out.println("---------*欢迎光临姝鲤鳅饭店*-------------"); 10 // 创建第一个订单 11 Order od1 = new Order(); 12 od1.setTableNum(1); 13 od1.setFood("土豆粉", 1); 14 od1.setFood("可乐", 2); 15 16 // 创建第二个订单 17 Order od2 = new Order(); 18 od2.setTableNum(3); 19 od2.setFood("糖醋排骨", 1); 20 od2.setFood("橙汁", 1); 21 22 // 创建厨师对象 23 SeniorChef sf = new SeniorChef(); 24 // 创建命令对象 25 OrderCommand cmd1 = new OrderCommand(sf,od1); 26 OrderCommand cmd2= new OrderCommand(sf,od2); 27 28 // 创建调用者(服务员) 29 Waiter waiter = new Waiter(); 30 waiter.setCommand(cmd1); 31 waiter.setCommand(cmd2); 32 33 // 让服务员发起命令 34 waiter.orderUp(); 35 } 36 37 }
运行结果:
4 责任链模式
4.1 定义
责任链模式又名职责链模式,为了避免请求发送者和多个请求处理这耦合在一起,将所有请求的处理者通过前一个对象记住下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
在日常生活中,常常会出现这样的情况:一个请求可以被多个对象处理,但每个对象的处理条件或权限不同。比如:一个员工要请假,应直接把请假单交给小组长审批,小组长能处理就审批,不能处理就将请假单转交给上一级进行审批,以此类推,直到有对象能够处理它。
4.2 结构
- 抽象处理者角色:定义一个处理请求的接口,包含抽象处理方法和一个后继链接。
- 具体处理者角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理则处理,否则将该请求转给它的后继者。
- 客户类角色:创建处理链,并向链头的具体处理者提交请求,它不关心处理细节和请求的传递过程。
4.3 优缺点
优点:
- 降低了耦合度:降低了请求发送者和请求接收者之间的耦合度。
- 增强了可扩展性:根据需要增加新的请求处理类,满足“开闭原则”。
- 增强了分配职责的灵活性:当工作流程发生变化,可以动态地改变链内的成员或者修改它们的顺序,也可以动态地新增或者删除责任。
- 简化了对象之间的连接:每一个对象只需要保持一个对其后继者的引用即可,避免了众多的条件判断语句。
- 责任分担:每个类只需要处理自己的工作即可,不能处理的传递给下一个对象完成,明确各类的责任,符合类的“单一职责原则”。
缺点:
- 不能保证每一个请求都能被处理,由于每一个请求都没有明确的接收者,该请求可能一直传到链的末端都不能被处理。
- 对于比较长的责任链,请求的处理将涉及到多个处理对象,会影响系统的性能。
- 职责链的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误而导致系统出错,例如:可能会造成循环调用。
4.4 使用场景
- 多个对象同时处理一个请求,但具体由哪个对象处理则在运行时动态决定。
- 在不明确接收者的情况下,向多个处理对象中的一个提交一个请求。
- 可动态执行一组对象处理请求。
4.5 【案例实现】 请假
请假一天以下的假只需要小组长统一即可;请假1天到3天的假还需要部门经理同意;请假3天到7天还需要总经理同意才行。
Handler类:
1 package my_test4; 2 /** 3 * 抽象处理者 4 * @author Asus 5 * 6 */ 7 public abstract class Handler { 8 protected final static int NUM_ONE=1; 9 protected final static int NUM_THREE=3; 10 protected final static int NUM_SEVEN=7; 11 // 该领导处理的请求天数 12 private int numStart; 13 private int numEnd; 14 // 声明后续者(声明上级领导) 15 private Handler nextHandler; 16 public Handler(int numStart) { 17 super(); 18 this.numStart = numStart; 19 } 20 public Handler(int numStart, int numEnd) { 21 super(); 22 this.numStart = numStart; 23 this.numEnd = numEnd; 24 } 25 // 设置上级领导对象 26 public void setNextHandler(Handler nextHandler) { 27 this.nextHandler = nextHandler; 28 } 29 //各级领导处理请假条的方法 30 protected abstract void handleLeave(LeaveRequest leave); 31 //提交请假条 32 public final void submit(LeaveRequest leave) { 33 //该领导进行审批 34 this.handleLeave(leave); 35 if(this.nextHandler!=null && leave.getNum()>this.numEnd) { 36 //提交给上级领导进行审批 37 this.nextHandler.submit(leave); 38 }else { 39 System.out.println("流程结束!"); 40 } 41 } 42 43 44 }
Manager类:
1 package my_test4; 2 /** 3 * 具体处理类(部门经理) 4 * @author Asus 5 * 6 */ 7 public class Manager extends Handler { 8 9 public Manager() { 10 super(Handler.NUM_ONE,Handler.NUM_THREE); 11 } 12 13 @Override 14 protected void handleLeave(LeaveRequest leave) { 15 System.out.println(leave.getName()+"请假"+leave.getNum()+"天,"+leave.getContent()+"。"); 16 System.out.println("部门经理审批:同意"); 17 } 18 19 }
GroupLeader类:
1 package my_test4; 2 /** 3 * 具体处理者 小组长类 4 * @author Asus 5 * 6 */ 7 public class GroupLeader extends Handler { 8 9 public GroupLeader() { 10 super(0,Handler.NUM_ONE); 11 12 } 13 14 @Override 15 protected void handleLeave(LeaveRequest leave) { 16 System.out.println(leave.getName()+"请假"+leave.getNum()+"天,"+leave.getContent()+"。"); 17 System.out.println("小组长审批:同意"); 18 19 } 20 21 }
GeneralManager 类:
1 package my_test4; 2 /** 3 * 具体处理者(总经理) 4 * @author Asus 5 * 6 */ 7 public class GeneralManager extends Handler{ 8 9 public GeneralManager() { 10 super(Handler.NUM_THREE,Handler.NUM_SEVEN); 11 } 12 13 @Override 14 protected void handleLeave(LeaveRequest leave) { 15 System.out.println(leave.getName()+"请假"+leave.getNum()+"天,"+leave.getContent()+"。"); 16 System.out.println("总经理审批:同意"); 17 } 18 19 }
LeaveRequest类:
1 package my_test4; 2 /** 3 * 请假条 4 * @author Asus 5 * 6 */ 7 public class LeaveRequest { 8 //姓名 9 private String name; 10 //请假天数 11 private int num; 12 //请假内容 13 private String content; 14 public LeaveRequest(String name, int num, String content) { 15 super(); 16 this.name = name; 17 this.num = num; 18 this.content = content; 19 } 20 public String getName() { 21 return name; 22 } 23 24 public int getNum() { 25 return num; 26 } 27 28 public String getContent() { 29 return content; 30 } 31 32 33 34 }
Client类:
1 package my_test4; 2 /** 3 * 客户端角色 4 * @author Asus 5 * 6 */ 7 public class Client { 8 public static void main(String[] args) { 9 // 创建一个请假条对象 10 LeaveRequest leave = new LeaveRequest("路轻尘",1,"身体不适"); 11 12 //创建各级领导对象 13 GroupLeader groupLeader = new GroupLeader(); 14 Manager manager = new Manager(); 15 GeneralManager generalManager = new GeneralManager(); 16 17 //设置处理者链 18 groupLeader.setNextHandler(manager); 19 manager.setNextHandler(generalManager); 20 21 //路轻尘提交请假申请 22 groupLeader.submit(leave); 23 } 24 25 }
运行结果:
5 状态模式
5.1 定义
对有状态的对象,把复杂的“逻辑判断”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
5.2 结构
- 环境角色:也称为上下文,定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态角色:实现抽象状态所定义的行为。
5.3 优缺点
优点
- 便于扩展:将所有的与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 替换条件语句:允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点
- 必然会增加系统的类和对象的个数。
- 结构和实现都较为复杂,如果使用不当,将导致程序结构和代码的混乱。
- 对“开闭原则”的支持不太好。
5.4 使用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
一个操作中含有庞大的分支结构,并且这些分支决定对象的状态时,可以考虑使用状态模式。
5.5 【案例实现】 电梯
通过按钮来控制一个电梯的状态,电梯状态有开门状态、关门状态、停止状态和停止状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如:处于运行状态时不能开门;处于停止状态才可以开门。
类图如下:
StoppingState类:
1 package my_test5; 2 /** 3 * 具体状态类(停止电梯) 4 * @author Asus 5 * 6 */ 7 public class StoppingState extends LiftState { 8 9 @Override 10 public void open() { 11 super.context.setLiftState(Context.opening); 12 super.context.open(); 13 } 14 15 @Override 16 public void close() { 17 super.context.setLiftState(Context.closing); 18 super.context.close(); 19 } 20 21 @Override 22 public void run() { 23 super.context.setLiftState(Context.running); 24 super.context.run(); 25 26 } 27 //这是当前状态要执行的方法 28 @Override 29 public void stop() { 30 System.out.println("电梯停止了..."); 31 32 } 33 34 }
RunningState:
1 package my_test5; 2 /** 3 * 具体状态类(运行电梯) 4 * @author Asus 5 * 6 */ 7 public class RunningState extends LiftState{ 8 9 @Override 10 public void open() { 11 // do nothing 12 13 } 14 15 @Override 16 public void close() { 17 //do nothing 18 19 } 20 //这是在运行状态下要实现的方法 21 @Override 22 public void run() { 23 System.out.println("电梯正在运行..."); 24 25 } 26 // 停止运行电梯 27 @Override 28 public void stop() { 29 super.context.setLiftState(Context.stoping); 30 super.context.stop(); 31 } 32 33 }
OpeningState:
1 package my_test5; 2 /** 3 * 具体状态类 打开电梯 4 * @author Asus 5 * 6 */ 7 public class OpeningState extends LiftState{ 8 //当前状态要执行的方法 9 @Override 10 public void open() { 11 System.out.println("电梯开启....."); 12 13 } 14 15 @Override 16 public void close() { 17 //修改状态 18 super.context.setLiftState(Context.closing); 19 //调用当前状态中的context中的close方法 20 super.context.close(); 21 22 } 23 24 @Override 25 public void run() { 26 //do nothing 27 28 } 29 30 @Override 31 public void stop() { 32 // do nothing 33 34 } 35 36 }
LiftState:
1 package my_test5; 2 3 /** 4 * 抽象状态类 5 * 6 * @author Asus 7 * 8 */ 9 public abstract class LiftState { 10 // 声明环境角色类变量 11 protected Context context; 12 13 public void setContext(Context context) { 14 this.context = context; 15 } 16 17 // 开启电梯操作 18 public abstract void open(); 19 20 // 关闭电梯操作 21 public abstract void close(); 22 23 // 电梯运行操作 24 public abstract void run(); 25 26 // 电梯停止操作 27 public abstract void stop(); 28 }
Context:
1 package my_test5; 2 /** 3 * 环境角色 4 * @author Asus 5 * 6 */ 7 public class Context { 8 //定义对应状态的常量 9 public final static OpeningState opening=new OpeningState(); 10 public final static ClosingState closing=new ClosingState(); 11 public final static RunningState running=new RunningState(); 12 public final static StoppingState stoping=new StoppingState(); 13 //定义一个当前电梯状态变量 14 private LiftState liftState; 15 public LiftState getLiftState() { 16 return liftState; 17 } 18 //设置当前状态对象 19 public void setLiftState(LiftState liftState) { 20 this.liftState = liftState; 21 //设置当前状态对象中的Context对象 22 this.liftState.setContext(this); 23 } 24 public void open() { 25 this.liftState.open(); 26 } 27 public void close() { 28 this.liftState.close(); 29 } 30 public void run() { 31 this.liftState.run(); 32 } 33 public void stop() { 34 this.liftState.stop(); 35 } 36 37 }
ClosingState:
1 package my_test5; 2 /** 3 * 具体实现类(关闭电梯) 4 * @author Asus 5 * 6 */ 7 public class ClosingState extends LiftState{ 8 9 @Override 10 public void open() { 11 super.context.setLiftState(Context.opening); 12 super.context.open(); 13 14 } 15 16 @Override 17 public void close() { 18 System.out.println("电梯门关闭..."); 19 20 } 21 22 @Override 23 public void run() { 24 super.context.setLiftState(Context.running); 25 super.context.run(); 26 27 } 28 29 @Override 30 public void stop() { 31 super.context.setLiftState(Context.stoping); 32 super.context.stop(); 33 34 } 35 36 }
Client:
1 package my_test5; 2 3 public class Client { 4 5 public static void main(String[] args) { 6 //创建环境角色对象 7 Context context = new Context(); 8 //设置当前电梯状态 9 context.setLiftState(new StoppingState()); 10 11 context.open(); 12 context.close(); 13 context.run(); 14 context.stop(); 15 } 16 17 }
运行结果:
6 观察者模式
6.1 定义
又被称为发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当这个主题对象发生变化时,会通知所有的观察者对象,使它们能够自动地更新自己。
6.2 结构
- 抽象主题角色:把所有的观察者保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题值提供一个接口,可以增加或者删除观察者对象。
- 具体主题角色:将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- 抽象观察者角色:是观察者的抽象类,定义了一个更新接口,以便在主题更改时更新自己。
- 具体观察者角色:实现抽象观察者定义的更新接口,以便在主题更改时更新自己的状态。
6.3 优缺点
优点
- 降低耦合度:降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 实现广播机制:当目标发送通知后,所有注册的观察者都会收到消息。
缺点
- 通知延迟:当观察者较多时,那么所有的观察者不能第一时间收到通知,有快的有慢的。
- 可能导致系统崩溃:如果目标有循环依赖的话,那么目标发送通知会使观察者循环调用,会导致系统崩溃。
6.4 使用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,使用观察者模式。
6.5 JDK源码实现
在Java中,通过java.util.Observable类和java.util.Observer接口定义了观察者模式,朱旭实现它们的子类就可以编写观察者模式实例。
Observable类是抽象目标类(被观察者),有一个Vector集合成员变量,用于保存所有要通知的观察者对象。
Observer接口使抽象观察者,监听目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用update方法,进行相应的工作。
6.6 【案例实现】 微信公众号
当关注的公众号更新的话,会推送给关注公众号的微信用户端,使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号就是被观察者,有多个的微信用户关注了程序猿这个公众号。类图如下:
7 中介者模式
7.1 定义
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
7.2 结构
- 抽象中介者角色:是中介者的接口,提供了同时对象注册与转发同时对象信息的抽象方法。
- 具体中介者角色:实现中介者接口,定义一个List来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
7.3 优缺点
优点
- 松散耦合:通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松耦合,基本上可以做到互补依赖,这样一来,同事对象就可以独立地变化和复用,不用再像以前那样“牵一发而动全身”了。
- 集中控制交互:多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么扩展中介者对象,而各个同事类不需要做修改。
- 一对多关联转为一对一关联:没使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者模式后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。
缺点
- 当同事类太多时,中介者的职责将很大,它会编的复杂而庞大,以至于系统难以维护。
7.4 使用场景
- 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
7.5 【案例实现】 租房
现在租房都是通过房产中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息,房屋中介充当租房者与房主的中介者。
类图如下:
8 迭代器模式
8.1 定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不是暴露聚合对象的内部显示。
8.2 结构
- 抽象聚合角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器角色:定义访问和遍历聚合元素的接口,通常包含hasNext()、next()等方法。
- 具体迭代器角色:实现抽象迭代器接口中所定义的方法,完成聚合对象的遍历,记录遍历的当前位置。
8.3 优缺点
优点
- 支持多种遍历:支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
- 简化了聚合类:由于引入了迭代器,在原有的聚合对象中不需要自行提供数据遍历等方法,这样可以简化聚合类的设计。
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无需修改原有代码,满足“开闭原则”的要求。
缺点
- 增加了类的个数,在一定程度上增加了系统的复杂性。
8.4 使用场景
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
8.5 【案例实现】学生管理系统
定义一个可以存储学生对象的容器对象,将遍历该容器的功能交给迭代器实现。
类图如下:
9 访问者模式
9.1 定义
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下,定义作用于这些元素的新的操作。
9.2 结构
- 抽象访问者角色:定义了对每一个元素访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者角色:给出每一个对元素类访问时所产生的具体行为。
- 抽象元素角色:定义了一个接受访问者的方法,其意义是指,每一个元素都要可以被访问者访问。
- 具体元素角色:提供接受访问方法的具体实现,而这个具体的实现,通常情况下,是使用访问者提供的访问该元素类的方法。
- 对象结构角色:定义当中所提到的对象结构,对象结构是一个抽象标书,具体点可以理解为一个具有容器性质或者符合对象特性的类,它会含有一组元素,并且可以迭代这些元素,供访问者访问。
9.3 优缺点
优点
- 扩展性好:在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
- 分离无关行为:通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
缺点
- 对象结构变化很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 违反了依赖倒置原则:依赖了具体类,而没有依赖抽象类。
9.4 使用场景
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
9.5 【案例实现】给宠物喂食
现在养宠物的人特别多,宠物分为猫,狗等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。
访问者角色:给宠物喂食的人
具体访问者角色:主人、其他人
抽象元素角色:动物抽象类
具体元素角色:宠物狗、宠物猫
结构对象角色:主人家
类图如下:
10 备忘录模式
10.1 定义
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时,能将该对象恢复到原先保存的状态。
10.2 结构
- 发起人角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里所有信息。
- 备忘录角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者模式:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个接口:窄接口、宽接口。
10.3 优缺点
优点
- 提供了一种可以恢复状态的机制,当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装,除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类:发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合“单一职责原则”。
缺点
- 资源消耗大:如果要保存的内部状态过多或者特别频繁,将会占用比较大的内存资源。
10.4 使用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如:word、记事本、数据库中的事务操作。
11 解释器模式
11.1 定义
定义语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”意思是使用规定格式和语法的代码,它是一种类行为型模式。
11.2 结构
- 抽象表达式角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法interpret()。
- 终结符表达式角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。l 客户端:主要任务是将需要分析的句子或表达式转换成使用的解释器对象苗顺的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
11.3 优缺点
优点
- 易于改变和扩展文法:由于在解释器模式中使用类来表示语言的文法规则,因此可通过继承等机制来改变或扩展文法,每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
- 实现文法较为容易:在抽象语法树种每一个表达式结点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。
- 增加新的解释表达式较为方便:如果用户需要增加新的解释表达式,只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类diamante无需修改,符合“开闭原则”。
缺点
- 对于复杂文法难以维护:在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。
- 执行效率较低:由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
11.4 使用场景
- 当语言的文法较为简单,且执行效率不是关键问题时,使用解释器模式。
- 当问题重复出现,且可以用一种简单的语言来进行表达式,使用解释器模式。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,使用解释器模式。