【设计模式】学习笔记(四)—— 行为型设计模式
行为型模式概述
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则“,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为:
以上 11 种行为型模式,除了模块方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
模块方法模式
概述:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
结构:
- 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模块方法和若干个基本方法构成。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又分为三种:
- 抽象方法(Abstract Method):一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法(Concrete Method):一个具体方法由一个抽象或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXXX,返回值类型为boolean类型。
- 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤
优点:
- 提示代码复用性(将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中)
- 实现了反向控制(通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为)
- 符合开闭原则
缺点:
- 对每个不同的实现都需要定义一个子类,者会导致类的个数增加,系统更加笼大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
适用场景:
- 算法的整体步骤很固定,但其个别部分易变时,这时候可以适用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
代码示例
【例】厨子炒菜
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式用代码来模拟一下
/**
* 抽象类(定义模板方法和基本方法)
*/
public abstract class AbstartClass {
//模板方法定义
public final void cookProcess(){
pourOil();
heatOil();
pourVegetable();
pourSauce();
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 AbstartClass{
public void pourVegetable() {
System.out.println("下锅的蔬菜是包菜");
}
public void pourSauce() {
System.out.println("下锅的酱料是辣椒");
}
}
/**
*炒菜心类(具体类)
*/
public class ConcreteClass_CaiXing extends AbstartClass{
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}
public void pourSauce() {
System.out.println("下锅的酱料是蒜蓉");
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//炒包菜
ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
//调用炒菜的功能
baoCai.cookProcess();
/**运行结果
* 倒油
* 热油
* 下锅的蔬菜是包菜
* 下锅的酱料是辣椒
* 炒啊炒啊炒啊炒到熟
*/
}
}
策略模式
概述:通过对算法进行封装,把适用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
结构:
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
优点:
- 策略类之间可以自由切换(策略类都实现了同一个接口)
- 易于扩展(增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码)
- 符合开闭原则
- 避免使用多重条件选择语句(if else),充分体现面向对象设计思路
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
适用场景:
- 一个系统需要动态地在几种算法中选择一个时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统哟啊求适用算法的客户不应该知道其操作的数据时,可适用策略模式来隐藏于算法相关的数据结构。
- 多个类只却别在表现行为不同,可以适用策略模式,在运行时动态选择具体要执行的行为。
代码示例
【例】促销活动
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。
/**
* 抽象策略类
*/
public interface Strategy {
void show();
}
/**
* 具体策略类
*/
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以下的商品");
}
}
/**
* 促销员(环境类)
*/
public class SalesMan {
//聚合策略类对象
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void salesManShow(){
strategy.show();
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//春节来了,使用春节促销活动
SalesMan salesMan = new SalesMan(new StrategyA());
//展示促销活动
salesMan.salesManShow();
System.out.println("========================");
//中秋节到了,使用中秋节促销活动
salesMan.setStrategy(new StrategyB());
//展示促销活动
salesMan.salesManShow();
System.out.println("========================");
//圣诞节到了,使用圣诞节促销活动
salesMan.setStrategy(new StrategyC());
//展示促销活动
salesMan.salesManShow();
/**
* 买一送一
* ========================
* 买200送50
* ========================
* 满1000元加一元换购任意200以下的商品
*/
}
}
命令模式
概述:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加于管理
结构:
- 抽象命令类(Command)角色:定义命令的接口,声明执行的方法。
- 具体命令(Concroto Command)角色:具体的命令,实现命令接口:通常会持有接收者,并调用接受者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
优点:
- 降低系统的耦合度(命令模式将调用操作的对象于实现该操作的对象解耦)
- 增加或删除命令非常方便(采用命令模式增加于删除命令不会影响其他类)
- 满足开闭原则
- 可以实现宏命令(命令模式可以于组合模式结合,将多个命令装配成一个组合命令)
- 方便实现Undo 和 Redo 操作(命令模式可以与备忘录模式结合,实现命令的撤销与恢复)
缺点:
- 可能会导致某些系统过多的具体命令类
- 系统结构更加复杂
适用场景:
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 系统需要在不同的时间指定请求,将请求排毒和执行请求
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作
代码示例
【例】日常吃饭
import java.util.HashMap;
import java.util.Map;
/**
* 订单类
*/
public class Order {
//餐桌号码
private int diningTable;
//所下的餐品及份数
private Map<String,Integer> foodDir=new HashMap<String,Integer>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDir() {
return foodDir;
}
public void setFoodDir(String name,int num) {
foodDir.put(name,num);
}
}
/**
* 抽象命令类
*/
public interface Command {
void execute();
}
import java.util.Map;
import java.util.Set;
public class OrderCommand implements Command {
//持有接收者对象
private SeniorChef receiver;
private Order order;
public OrderCommand(SeniorChef seniorChef, Order order) {
this.receiver = seniorChef;
this.order = order;
}
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Map<String,Integer> foodDir = order.getFoodDir();
//遍历map集合
Set<String> keys=foodDir.keySet();
for (String footName:keys){
receiver.makeFood(footName,foodDir.get(footName));
}
System.out.println(order.getDiningTable() + "桌的饭准备完毕!!!");
}
}
/**
* 厨师类(命令类)
*/
public class SeniorChef {
public void makeFood(String name,int num){
System.out.println(num + "份" + name);
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 服务员类(属于请求者角色)
*/
public class Waitor {
//持有多个命令对象
private List<Command> commands = new ArrayList<>();
public void setCommands(Command cmd){
//将cmd对象存储到list集合中
commands.add(cmd);
}
//发起命令功能 喊 订单来了
public void orderUp(){
System.out.println("美女服务员说:大厨,新订单来了....");
//遍历List集合
for(Command command:commands){
if (command != null){
command.execute();
}
}
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建第一个订单对象
Order order1 = new Order();
order1.setDiningTable(1);
order1.setFoodDir("西红柿鸡蛋面",1);
order1.setFoodDir("小杯可乐",2);
//创建第二个订单对象
Order order2 = new Order();
order2.setDiningTable(2);
order2.setFoodDir("尖椒肉丝盖饭",1);
order2.setFoodDir("小杯橙汁",1);
//创建厨师对象
SeniorChef seniorChef = new SeniorChef();
//创建命令对象
OrderCommand cmd1=new OrderCommand(seniorChef,order1);
OrderCommand cmd2=new OrderCommand(seniorChef,order2);
//创建调用者对象(服务员对象)
Waitor invoke = new Waitor();
invoke.setCommands(cmd1);
invoke.setCommands(cmd2);
//让服务员发起命令
invoke.orderUp();
/**调用结果
* 美女服务员说:大厨,新订单来了....
* 1桌的订单:
* 1份西红柿鸡蛋面
* 2份小杯可乐
* 1桌的饭准备完毕!!!
* 2桌的订单:
* 1份尖椒肉丝盖饭
* 1份小杯橙汁
* 2桌的饭准备完毕!!!
*/
}
}
责任链模式(职责链模式)
定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,知道有对象处理它为止。
结构:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方式,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:场景处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
优点:
- 降低了对象之间的耦合度(该模式降低了请求发送者和接收者的耦合度)
- 增强了系统的可扩展性(可以根据需要增加新的请求处理类,满足开闭原则)
- 增加了给对象指派职责的灵活性(当工作流程发生变化,可以动态地改变链内的采用或者修改它们的次序,也可以动态地新增或者删除责任)
- 责任链简化了对象之间的连接(一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,者避免了是适用众多的 if 或者 if...else语句)
- 责任分担(每个类只需要处理自己该处理的工作,不能处理的传递给下一给对象完成,明确各类的责任范围)
- 符合单一责任原则
缺点:
- 不能保证每个请求一定被处理(请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链末端都得不到处理)
- 对比较长的责任链,请求的处理可能涉及多个处理对象,系统性能受到一定影响
- 责任链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于责任链的错误设置而导致系统出错,如可能会造成循环调用
适用场景:
- 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
- 可动态指定一组对象处理请求,或添加新的处理者。
- 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
代码示例
【例】请假流程小程序
请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天的假需要总经理同意才行。
/**
* 请假条类
*/
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 = 2;
protected final static int NUM_SEVEN = 3;
//该领导处理的请求天数区间
private int numStart;
private int numEnd;
//声明后继者(声明上级领导)
public 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;
}
//各级领导处理请求条的方法
protected abstract void handleLeave(LeaveRequest leaveRequest);
//提交请求
public final void submit(LeaveRequest leave){
//该领导进行审批
this.handleLeave(leave);
if(this.nextHandler != null && leave.getNum() > this.numEnd){
//提交给上级领导进行审批
this.nextHandler.submit(leave);
}else {
System.out.println("流程结束!");
}
}
}
/**
* 小组长类(具体处理者)
*/
public class GroupLeader extends Handler{
public GroupLeader(){
super(0,Handler.NUM_ONE);
}
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天" + leaveRequest.getContent() + "。");
System.out.println("小组长审批:同意");
}
}
/**
* 部门经理类(具体处理者)
*/
public class Manager extends Handler{
public Manager(){
super(Handler.NUM_ONE,Handler.NUM_THREE);
}
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天" + leaveRequest.getContent() + "。");
System.out.println("部门经理审批:同意");
}
}
/**
* 总经理类(具体处理者)
*/
public class GeneralManager extends Handler{
public GeneralManager(){
super(Handler.NUM_THREE,Handler.NUM_SEVEN);
}
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天" + leaveRequest.getContent() + "。");
System.out.println("总经理审批:同意");
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建一个请假条对象
LeaveRequest leave = new LeaveRequest("小明", 2, "身体不适");
//创建各级领导对象
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
//设置处理者链
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
//小明提交请假申请
groupLeader.submit(leave);
/**运行结果 - 通过改变请假时间,申请将由不同的领导审批
* 小明请假2天身体不适。
* 小组长审批:同意
* 小明请假2天身体不适。
* 部门经理审批:同意
* 流程结束!
*/
}
}
状态模式
定义:对有状态的对象,把复杂的“逻辑判断”提取到不同的状态对象中,允许状态对对象再内部状态发生改变时改变其行为
结构:
- 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将于状态相关的操作委托给当前状态对象来处理
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为
优点:
- 将所有与某个状态有个的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点:
- 状态模式的适用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都比较复杂,如果适用不恰当将导致程序结构和代码的混乱。
- 状态模式对“开闭原则”的支持并不太好
适用场景:
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑适用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
代码示例
【例】抽奖活动
假如每次参加一次这个活动要扣除用户50积分,中奖概率是10%,奖品数量固定,抽完就不能抽了。活动有四个状态:可以抽奖、不能抽奖、发放奖品、奖品领完
/**
* 状态抽象类
*/
public abstract class State {
// 扣除积分 -50
public abstract void deductMoney();
// 是否抽中奖品
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
/**
* 不能抽奖状态
*/
public class NoRaffleState extends State{
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public NoRaffleState(RaffleActivity activity){
this.activity = activity;
}
// 当前状态可以扣积分,扣除后,将状态设置成可抽奖状态
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
// 当前状态不能抽奖
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔!");
return false;
}
// 当前状态不能发奖品
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
import java.util.Random;
/**
* 可以抽奖的状态
*/
public class CanRaffleState extends State{
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity){
this.activity = activity;
}
// 已经扣除了积分,不能再扣了
public void deductMoney() {
System.out.println("已经扣取过了积分");
}
// 可以抽奖,根据抽奖结果更改状态
public boolean raffle() {
System.out.println("正在抽奖,请稍等!");
Random r = new Random();
int num = r.nextInt(10);
// 10%中奖机会
if(num == 0){
// 改变活动状态为发放奖品
activity.setState(activity.getDispenseState());
return true;
}else {
System.out.println("很遗憾你没抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
return false;
}
}
// 不能发放奖品
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}
/**
* 发送奖品状态
*/
public class DispenseState extends State{
// 初始化时传入活动引用,发放奖品后改变其状态
RaffleActivity activity;
public DispenseState(RaffleActivity activity){
this.activity = activity;
}
public void deductMoney() {
System.out.println("不能扣除积分");
}
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
//发放奖品
public void dispensePrize() {
if(activity.getCount() > 0){
System.out.println("恭喜中奖了");
//改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
}else {
System.out.println("很疑惑,奖品发完啦");
//改变状态为奖品发放完,后面就不可以再抽奖了
activity.setState(activity.getDispensOutState());
}
}
}
/**
* 奖品发放完毕状态
* 说明,当activity 改变成 DispenseOutState,抽奖活动结束
*/
public class DispenseOutState extends State{
// 初始化时传入活动引用
RaffleActivity activity;
public DispenseOutState(RaffleActivity activity){
this.activity = activity;
}
public void deductMoney() {
System.out.println("奖品发放完了,请下次再参加");
}
public boolean raffle() {
System.out.println("奖品发放完了,请下次再参加");
System.exit(0);
return false;
}
public void dispensePrize() {
System.out.println("奖品发放完了,请下次再参加");
}
}
/**
* 抽奖活动
*/
public class RaffleActivity {
//活动当前状态,是变化的
State state = null;
//奖品数量
int count = 0;
//四个属性表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);
//构造器
//1. 初始化当前的状态为 noRafflleState(不能抽奖状态)
//2. 初始化奖品数量
public RaffleActivity(int count){
this.state = getNoRafflleState();
this.count = count;
}
//扣分,调用当前状态的 deductMoney
public void deductMoney(){
state.deductMoney();
}
//抽奖
public void raffle(){
//如果当前状态是抽奖成功
if(state.raffle()){
//领取奖品
state.dispensePrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
//每领取一次奖品,count--
public int getCount() {
int Curnt = count--;
return Curnt;
}
public void setCount(int count) {
this.count = count;
}
public State getNoRafflleState() {
return noRafflleState;
}
public void setNoRafflleState(State noRafflleState) {
this.noRafflleState = noRafflleState;
}
public State getCanRaffleState() {
return canRaffleState;
}
public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}
public State getDispenseState() {
return dispenseState;
}
public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}
public State getDispensOutState() {
return dispensOutState;
}
public void setDispensOutState(State dispensOutState) {
this.dispensOutState = dispensOutState;
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);
for (int i = 0; i < 300; i++){
System.out.println("--------第" + (i+1) + "次抽奖--------");
//参加抽奖,第一步点击扣除积分
activity.deductMoney();
//第二步抽奖
activity.raffle();
}
/**运行结果
* --------第1次抽奖--------
* 扣除50积分成功,您可以抽奖了
* 正在抽奖,请稍等!
* 很遗憾你没抽中奖品!
* --------第2次抽奖--------
* 扣除50积分成功,您可以抽奖了
* 正在抽奖,请稍等!
* 很遗憾你没抽中奖品!
* --------第3次抽奖--------
* 扣除50积分成功,您可以抽奖了
* 正在抽奖,请稍等!
* 很遗憾你没抽中奖品!
* --------第4次抽奖--------
* 扣除50积分成功,您可以抽奖了
* 正在抽奖,请稍等!
* 恭喜中奖了
* --------第5次抽奖--------
* 扣除50积分成功,您可以抽奖了
* 正在抽奖,请稍等!
* 很疑惑,奖品发完啦
* --------第6次抽奖--------
* 奖品发放完了,请下次再参加
* 奖品发放完了,请下次再参加
*/
}
}
观察者模式
概述:又被称为发布 订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态变化时,会通知所有的观察者对象,使它们能够自动更新自己
结构:
- 抽象主题/抽象被观察者(Subject):抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题/具体被观察者(ConcreteSubject):该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者模式发送通知。
- 抽象观察者(Observer):是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己
- 具体观察者(ConcrereObserver):实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身状态
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】
缺点:
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
适用场景:
- 对象间存在一对多关系,一个对象的状态发送变化会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时。
代码示例
【例】微信公众号
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序媛公众号
/**
* 抽象观察者类
*/
public interface Observer {
void updata(String message);
}
/**
* 抽象主题角色
*/
public interface Subject {
//添加订阅者(添加观察者对象)
void attach(Observer observer);
//删除订阅者
void detach(Observer observer);
//通知订阅者更新消息
void notify(String message);
}
/**
* 具体的观察者角色类
*/
public class WeiXinUser implements Observer {
private String name;
public WeiXinUser(String name) {
this.name = name;
}
public void updata(String message) {
System.out.println(name + "-" + message);
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 具体主题角色类
*/
public class SubscriptionSubject implements Subject{
//定义一个集合用来存储多个观察者对象
private List<Observer> weixinUserList = new ArrayList<>();
public void attach(Observer observer) {
weixinUserList.add(observer);
}
public void detach(Observer observer) {
weixinUserList.remove(observer);
}
public void notify(String message) {
//遍历集合
for (Observer observer : weixinUserList) {
//调用观察者对象中的updata方法
observer.updata(message);
}
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//1.创建公众号对象
SubscriptionSubject subject = new SubscriptionSubject();
//2.订阅公众号
subject.attach(new WeiXinUser("石头人"));
subject.attach(new WeiXinUser("小天使"));
subject.attach(new WeiXinUser("司马浩"));
//3.公众号更新,发出消息给订阅者(观察者对象)
subject.notify("想要代码敲得号,早睡早起吃饱饱");
/**运行结果
* 石头人-想要代码敲得号,早睡早起吃饱饱
* 小天使-想要代码敲得号,早睡早起吃饱饱
* 司马浩-想要代码敲得号,早睡早起吃饱饱
*/
}
}
中介者模式(调停模式)
概述:定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互
结构:
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同时对象信息的抽象方法
- 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同时角色
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有互相影响的同时类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互
优点:
- 松散耦合(中介者模式通过把多个对象之间的互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样依赖,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了)
- 集中控制交互(多个同事对象的交互,被封装再中介者对象里面集中管理,使得这些交互行为发送变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改)
- 一个多关联转变为一对一的关联(没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象的关系通常变成双向的一对一,这会让对象的关系更加容易理解和实现)
缺点:
- 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护
适用场景:
- 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
代码示例
【例】租房
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而房屋者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者
/**
* 抽象同事角色类
*/
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
}
/**
* 抽象中介者类
*/
public abstract class Mediator {
public abstract void constact(String message,Person person);
}
/**
* 房主类(具体同事角色类)
*/
public class HouseOwner extends Person{
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
//和中介联系(沟通)
public void constact(String message){
mediator.constact(message,this);
}
//获取信息
public void getMessage(String message){
System.out.println("房主" + name +"获取到的信息是:" + message);
}
}
/**
* 租客类(具体同事角色类)
*/
public class Tenant extends Person{
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
//和中介联系(沟通)
public void constact(String message){
mediator.constact(message,this);
}
//获取信息
public void getMessage(String message){
System.out.println("租房者" + name +"获取到的信息是:" + message);
}
}
/**
* 具体中介者角色类
*/
public class MediatorStructure extends Mediator{
//聚合房主和具体租房者对象
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
public void constact(String message, Person person) {
if(person == houseOwner){
tenant.getMessage(message);
}else {
houseOwner.getMessage(message);
}
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建中介者对象
MediatorStructure mediator = new MediatorStructure();
//创建租房者对象
Tenant tenant = new Tenant("李四",mediator);
//创建房主对象
HouseOwner houseOwner = new HouseOwner("张三",mediator);
//中介者要知道具体的房主和租房者
mediator.setTenant(tenant);
mediator.setHouseOwner(houseOwner);
tenant.constact("我要租三室的房子!!!");
houseOwner.constact("我这有三室的房子你要租嘛?");
/**运行结果
* 房主张三获取到的信息是:我要租三室的房子!!!
* 租房者李四获取到的信息是:我这有三室的房子你要租嘛?
*/
}
}
迭代器模式
概述:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
结构:
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的示例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next()等方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
优点:
- 它支持一下不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式(在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式)
- 迭代器简化了聚合类(由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计)
- 在迭代器模式中,由于引入抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码
- 满足开闭原则
缺点:
增加了类的个数,在一定程度上增加了系统的复杂性。
适用场景:
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无需暴露起内部细节的表示时。
代码示例
【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现
/**
* 学生类
*/
public class Student {
private String name;
private String number;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Student(String name, String number) {
this.name = name;
this.number = number;
}
public Student(){
}
}
/**
* 抽象迭代器角色接口
*/
public interface StudentIterator {
//判断是否还有元素
boolean hasNext();
//获取下一个元素
Student next();
}
import java.util.ArrayList;
import java.util.List;
/**
* 具体迭代器角色类
*/
public class StudentAggregateImpl implements StudentAggregate{
private List<Student> list = new ArrayList<>();
public void addStudent(Student stu) {
list.add(stu);
}
public void removeStudent(Student stu) {
list.remove(stu);
}
//获取迭代器对象
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}
/**
* 抽象聚合角色接口
*/
public interface StudentAggregate {
//添加学生功能
void addStudent(Student stu);
//删除学生功能
void removeStudent(Student stu);
//获取迭代器对象功能
StudentIterator getStudentIterator();
}
import java.util.ArrayList;
import java.util.List;
/**
* 具体聚合角色类
*/
public class StudentAggregateImpl implements StudentAggregate{
private List<Student> list = new ArrayList<>();
public void addStudent(Student stu) {
list.add(stu);
}
public void removeStudent(Student stu) {
list.remove(stu);
}
//获取迭代器对象
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建聚合对象
StudentAggregateImpl aggregate =new StudentAggregateImpl();
//添加元素
aggregate.addStudent(new Student("张三","001"));
aggregate.addStudent(new Student("李四","002"));
aggregate.addStudent(new Student("王五","003"));
aggregate.addStudent(new Student("赵六","004"));
//遍历聚合对象
//1.获取迭代器对象
StudentIterator iterator = aggregate.getStudentIterator();
//2.遍历
while (iterator.hasNext()){
//3.获取元素
Student student = iterator.next();
System.out.println(student.toString());
}
/**运行结果
* Student{name='张三', number='001'}
* Student{name='李四', number='002'}
* Student{name='王五', number='003'}
* Student{name='赵六', number='004'}
*/
}
}
访问者模式
概述:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作
结构:
- 抽象访问者(Visitor)角色:定义了对每一种元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
- 抽象元素(Element)角色:定义了一个接受访问者的方法(accept)其意义是指,每一个元素都要可以被访问者访问。
- 具体元素(ConcreteElement)角色:提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素的方法。
- 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者符合对象特征的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。
优点:
- 扩展性好(在不改变对象结构中的元素的情况下,为对象结构中的元素添加新的功能)
- 复用性好(通过访问者来定义整个对象结构通用的功能,从而提高复用程度)
- 分离无关行为(通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一)
缺点:
- 对象结构变化很困难(在访问者模式中,没增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作)
- 违背了开闭原则
- 违反了依赖倒置原则(访问者模式依赖了具体类,而没有依赖抽象类)
适用场景:
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构
代码示例
【例】给宠物喂食
现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗、猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。
- 访问者角色:给宠物喂食的人
- 具体访问者角色:主人、其他人
- 抽象元素角色:动物抽象类
- 具体元素角色:宠物狗、宠物猫
- 结构对象角色:主人家
/**
* 抽象访问者角色类
*/
public interface Person {
//喂食宠物猫
void feed(Cat cat);
//喂食宠物狗
void feed(Dog dog);
}
/**
* 抽象元素角色类
*/
public interface Animal {
//接受访问者访问的功能
void accept(Person person);
}
/**
* 宠物猫(具体元素角色类)
*/
public class Cat implements Animal{
public void accept(Person person) {
person.feed(this);//访问者给宠物猫喂食
System.out.println("很好吃,喵喵喵");
}
}
/**
* 宠物狗(具体元素角色类)
*/
public class Dog implements Animal{
public void accept(Person person) {
person.feed(this);
System.out.println("很好吃,汪汪汪");
}
}
/**
* 自己(具体访问者角色类)
*/
public class Owner implements Person{
public void feed(Cat cat) {
System.out.println("主人喂食猫");
}
public void feed(Dog dog) {
System.out.println("主人喂食狗");
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 对象结构类
*/
public class Home {
//声明一个集合对象,用来存储元素对象
private List<Animal> nodeList = new ArrayList<>();
//添加元素功能
public void add(Animal animal){
nodeList.add(animal);
}
public void action(Person person){
//遍历集合获取每一个元素,让访问者访问每一个元素
for (Animal animal : nodeList) {
animal.accept(person);
}
}
}
/**
*其他人(具体访问者角色类)
*/
public class Someone implements Person{
public void feed(Cat cat) {
System.out.println("其他人喂食猫");
}
public void feed(Dog dog) {
System.out.println("其他人喂食狗");
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建Home对象
Home home = new Home();
//添加元素到Home对象中
home.add(new Dog());
home.add(new Cat());
//创建主人对象
Owner owner = new Owner();
//让主人喂食所有的宠物
home.action(owner);
/**运行结果
* 主人喂食狗
* 很好吃,汪汪汪
* 主人喂食猫
* 很好吃,喵喵喵
*/
}
}
备忘录模式
概述:又称快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
结构:
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存于获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口:
- 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许它把备忘录对象传给其他的对象。
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
优点:
- 提供了一种可以恢复状态的机制(当用户需要时能够比较方便地将数据恢复到某个历史的状态)
- 实现了内部状态的封装(除了创建它的发起人之外,其他对象都不能够访问这些状态信息)
- 简化了发起人类(发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理)
- 符合单一职责原则
缺点:
- 资源消耗大(如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源)
适用场景:
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能
- 需要提供一个可回滚操作的场景,如 word、记事本等软件软件编辑时按 Ctrl+z 组合键,还有数据库中事务操作
代码示例
【例】游戏挑战Boss
游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决定的效果不理想可以让游戏恢复到战斗之前的状态
要实现上述案例,有两种方式:
- “白箱”备忘录模式
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态对所有对象公开
分析:白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。
/**
* 游戏角色类(属于发起人角色)
*/
public class GameRole {
private int vit;//生命力
private int atk;//攻击力
private int def;//防御力
//初始化内部状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//战斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色状态功能
public RoleStateMemento saveState() {
return new RoleStateMemento(vit, atk, def);
}
//恢复角色状态
public void recoverState(RoleStateMemento roleStateMemento) {
//将备忘录对象中存储的状态赋值给当前对象的成员
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//展示状态功能
public void stateDisplay() {
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
/**
* 备忘录对象管理对象
*/
public class RoleStateCaretaker {
//声明RoleStateMemento类型的变量
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}
/**
* 备忘录角色类
*/
public class RoleStateMemento {
private int vit;//生命力
private int atk;//攻击力
private int def;//防御力
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
public RoleStateMemento() {
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
System.out.println("--------------大战Boss前--------------");
//创建游戏角色对象
GameRole gameRole = new GameRole();
gameRole.initState();//初始化状态操作
gameRole.stateDisplay();
//将该角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("--------------大战Boss后--------------");
//损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("--------------恢复之前状态--------------");
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
gameRole.stateDisplay();
/**运行结果
* --------------大战Boss前--------------
* 角色生命力:100
* 角色攻击力:100
* 角色防御力:100
* --------------大战Boss后--------------
* 角色生命力:0
* 角色攻击力:0
* 角色防御力:0
* --------------恢复之前状态--------------
* 角色生命力:100
* 角色攻击力:100
* 角色防御力:100
*/
}
}
- “黑箱”备忘录模式
备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类
/**
* 游戏角色类(属于发起人角色)
*/
public class GameRole {
private int vit;//生命力
private int atk;//攻击力
private int def;//防御力
//初始化内部状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//战斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色状态功能
public Memento saveState() {
return new RoleStateMemento(vit, atk, def);
}
//恢复角色状态
public void recoverState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento)memento;
//将备忘录对象中存储的状态赋值给当前对象的成员
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//展示状态功能
public void stateDisplay() {
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
private class RoleStateMemento implements Memento{
private int vit;//生命力
private int atk;//攻击力
private int def;//防御力
public RoleStateMemento() {
}
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
}
/**
* 备忘录接口,对外提供窄接口
*/
public interface Memento {
}
/**
* 备忘录对象管理对象
*/
public class RoleStateCaretaker {
//声明RoleStateMemento类型的变量
private Memento Memento;
public Memento getMemento() {
return Memento;
}
public void setMemento(Memento memento) {
Memento = memento;
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
System.out.println("--------------大战Boss前--------------");
//创建游戏角色对象
GameRole gameRole = new GameRole();
gameRole.initState();//初始化状态操作
gameRole.stateDisplay();
//将该角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker( );
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("--------------大战Boss后--------------");
//损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("--------------恢复之前状态--------------");
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
/**运行
* --------------大战Boss前--------------
* 角色生命力:100
* 角色攻击力:100
* 角色防御力:100
* --------------大战Boss后--------------
* 角色生命力:0
* 角色攻击力:0
* 角色防御力:0
* --------------恢复之前状态--------------
* 角色生命力:100
* 角色攻击力:100
* 角色防御力:100
*/
}
}
解析器模式
概述:给定一个语言,表示它的文法表示,并定义一个解析器,这个解析器适用该表示来解析语言中的句子。在解释器模式中,我们需要将带解决的问题,提取出规则,抽象为一种"语言"。比如:加减法运算,规则:由数值和+-符号组成的合法序列,"1+3-2" 就是这种语言的句子。解析器就是要解析出来语句的含义。
文法(语法)规则:
文法是用于描述语言的语法结构的形式规则。
expression ::=value | plus | minus
plus ::= expression '+' expression
minus ::= expression '-' expression
value ::= integer
注意:这里的符号"::="表示"定义为"的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法
上面规则描述为:
表达式可以是一个指,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,指的类型为整型数。
抽象语法树:
在计算机科学中,抽象语法数(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编译语言的语法结构,树上的每个节点都表示源代码中的一种结构。
用树形来表示符合文法规则的句子
结构:
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpet()
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式之相对应
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的没条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通过包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解析器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
优点:
- 易于改变和扩展文法(由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言)
- 实现文法较为容易(在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂)
- 增加新的解释表达式较为方便(如果用户需要新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”)
缺点:
- 对于复杂文法难以维护(在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护)
- 执行效率较低(由于在解析器模式中使用了大量的循环和递归调用,因此在解析较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦)
适用场景:
- 当语言的文法较为简单,且执行效率不是关键问题时
- 当问题重复出现,且可以用一种简单的语言来进行表达时
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树时
代码示例
【例】设计实现加减法的软件
/**
* 抽象表达式类
*/
public abstract class AbstractExpression {
public abstract int interpret(Context context);
}
import java.util.HashMap;
import java.util.Map;
/**
* 环境角色类
*/
public class Context {
//定义一个Map集合用来存储变量及对应的值
private Map<Variable, Integer> map = new HashMap<Variable, Integer>();
//添加变量的功能
public void assign(Variable var, Integer value) {
map.put(var, value);
}
//根据变量获取对应的值
public int getValue(Variable var) {
return map.get(var);
}
}
/**
* 封装变量类
*/
public class Variable extends AbstractExpression{
//声明存储变量名的成员变量
private String name;
public Variable(String name) {
this.name = name;
}
public int interpret(Context context) {
//返回变量的值
return context.getValue(this);
}
@Override
public String toString() {
return name;
}
}
/**
* 减法表达式类
*/
public class Minus extends AbstractExpression {
//-号左边的表达式
private AbstractExpression left;
//-号右边的表达式
private AbstractExpression right;
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
public int interpret(Context context) {
//将左边表达式的结构和右边表达式的结构进行相减
return left.interpret(context) - right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "-" + right.toString() + ")";
}
}
/**
* 加法表达式类
*/
public class Plus extends AbstractExpression {
//+号左边的表达式
private AbstractExpression left;
//+号右边的表达式
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
public int interpret(Context context) {
//将左边表达式的结构和右边表达式的结构进行相加
return left.interpret(context) + right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "+" + right.toString() + ")";
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建环境对象
Context context = new Context();
//创建多个变量对象
Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
//将变量存储到环境对象中
context.assign(a, 1);
context.assign(b, 2);
context.assign(c, 3);
context.assign(d, 4);
//获取抽象语法树 a + b - c + d
AbstractExpression expression = new Minus(a, new Plus(new Minus(b, c), d));
//解析(计算)
int result = expression.interpret(context);
System.out.println(expression + "= " + result);
/**运行结果
* (a-((b-c)+d))= -2
*/
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示