行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式
和对象行为模式
,前者采用继承
机制来在类间分派行为,后者采用组合或聚合
在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为:
模板方法模式
策略模式
命令模式
职责链模式
状态模式
观察者模式
中介者模式
迭代器模式
访问者模式
备忘录模式
解释器模式
以上 11 种行为型模式,除了模板方法模式
和解释器模式
是类行为型模式
,其他的全部属于对象行为型模式
。
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义:
定义一个操作中的算法骨架
,而将算法的一些步骤延迟到子类中
,使得子类可以不改变该算法结构
的情况下重新定义该算法的某些特定步骤
。
模板方法(Template Method)模式包含以下主要角色:
【例】炒菜
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
代码如下:
// 抽象类:定义模板方法和基本方法
public abstract class AbstractClass {
// 模板方法定义(不能通过继承改变算法结构,加final)
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 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();
System.out.println("===================");
// 炒蒜蓉菜心
ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
caiXin.cookProcess();
}
}
注意:为防止恶意操作,一般模板方法都加上 final 关键词。
优点:
缺点:
InputStream类就使用了模板方法模式。在InputStream类中定义了多个read()
方法,如下:
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(); // 调用了无参的read方法,该方法是每次读取一个字节数据,本质是调用了子类中的read方法
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read(); // 算法结构的执行:多次调用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个字节数据。具体如何读取一个字节数据呢?由子类实现。
先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。
作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装
,把使用算法的责任
和算法的实现
分割开来,并委派给不同的对象对这些算法进行管理
。
策略模式的主要角色如下:
【例】促销活动
一家百货公司在定年度的促销活动。针对不同的特殊节日(春节、618、双11)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
代码如下:
定义抽象策略类:百货公司所有促销活动的共同接口
// 抽象策略类
public interface Strategy {
void show();
}
定义具体策略角色(Concrete Strategy):每个节日具体的促销活动
// 具体策略类:封装算法
public class StrategyA implements Strategy {
// 为春节准备的促销活动A
@Override
public void show() {
System.out.println("春节活动:买一送一");
}
}
// 具体策略类:封装算法
public class StrategyB implements Strategy {
// 为618准备的促销活动B
@Override
public void show() {
System.out.println("618活动:满200元减50元");
}
}
// 具体策略类:封装算法
public class StrategyC implements Strategy {
// 为双11准备的促销活动C
@Override
public void show() {
System.out.println("双11活动:满1000元加一元换购任意200元以下商品");
}
}
定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
// 促销员:环境类
public class SalesMan {
// 聚合策略类对象
private Strategy strategy;
// 构造器注入
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
// set方法注入
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();
// 618促销活动
salesMan.setStrategy(new StrategyB());
salesMan.salesManShow();
// 双11促销活动
salesMan.setStrategy(new StrategyC());
salesMan.salesManShow();
}
}
Comparator
中的策略模式。在Arrays类中有一个 sort()
方法,如下:
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根据这个策略来进行排序。就比如下面的测试类。
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)); // [12, 5, 4, 3, 2, 2, 1]
}
}
这里我们在调用Arrays的sort方法时,第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays类的sort方法到底有没有使用Comparator子实现类中的 compare()
方法吗?让我们继续查看TimSort类的 sort()
方法,代码如下:
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; // Arrays of size 0 and 1 are always sorted
// If array is small, do a "mini-TimSort" with no merges
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;
// Find end of run, and reverse range if descending
if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
runHi++;
reverseRange(a, lo, runHi);
} else { // Ascending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
runHi++;
}
return runHi - lo;
}
}
上面的代码中最终会跑到 countRunAndMakeAscending()
这个方法中。我们可以看见,只用了compare方法,所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行,这也是Comparator接口中必须要子类实现的一个方法。
定义:
将一个请求封装为一个对象
,使发出请求
的责任和执行请求
的责任分割开
。两者之间
通过命令对象
进行沟通
,这样方便将命令对象进行存储、传递、调用、增加与管理
。
命令模式包含以下主要角色:
将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。
服务员: 就是调用者角色,由她来发起命令。
资深大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。
代码如下:
// 抽象命令类
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;
}
@Override
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Map<String, Integer> foodDir = order.getFoodDir();
Set<String> names = foodDir.keySet();
for (String name : names) {
receiver.makeFood(name, foodDir.get(name));
}
System.out.println(order.getDiningTable() + "桌的餐品制作完成!服务员请上菜\n");
}
}
// 订单类
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 setFood(String name, int num) {
foodDir.put(name, num);
}
}
// 厨师类:命令接收者
public class SeniorChef {
public void makeFood(String name, int num) {
System.out.println(num + "份" + name);
}
}
// 服务员类:调用者/请求者角色
public class Waiter {
// 持有多个命令对象
private List<Command> commands = new ArrayList<Command>();
public void setCommand(Command cmd) {
commands.add(cmd);
}
// 发起命令请求的功能
public void orderUp() {
System.out.println("服务员:新订单来了");
for (Command command : commands) {
if (command != null) {
command.execute();
}
}
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 创建订单1
Order order1 = new Order();
order1.setDiningTable(1);
order1.setFood("西红柿鸡蛋面", 1);
order1.setFood("小杯可乐", 2);
// 创建订单2
Order order2 = new Order();
order2.setDiningTable(2);
order2.setFood("尖椒肉丝盖饭", 1);
order2.setFood("小杯橙汁", 1);
// 创建接收者(厨师)对象
SeniorChef receiver = new SeniorChef();
// 创建命令对象
OrderCommand cmd1 = new OrderCommand(receiver, order1);
OrderCommand cmd2 = new OrderCommand(receiver, order2);
// 创建调用者(服务员)对象
Waiter invoker = new Waiter();
invoker.setCommand(cmd1);
invoker.setCommand(cmd2);
// 服务员发起命令
invoker.orderUp();
}
}
Runable是一个典型命令模式,Runnable担当抽象命令角色,Thread充当的是调用者,start方法就是其执行方法
// 命令接口(抽象命令角色)
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(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体命令角色
*/
public class TurnOffThread implements Runnable{
private Receiver receiver;
public TurnOffThread(Receiver receiver) {
this.receiver = receiver;
}
public void run() {
receiver.turnOFF();
}
}
public class Receiver { ... }
/**
* 测试类
*/
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();
}
}
在现实生活中,常常会出现这样的事例:**一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。**例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。
定义:
又名职责链模式
,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
职责链模式主要包含以下角色:
现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天以上还需要总经理同意才行。
代码如下:
// 请假条类
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;
}
// 各级领导处理请假条的方法
protected abstract void handleLeave(LeaveRequest leave);
// 提交请假条
public final void submit(LeaveRequest leave) {
if (leave.getNum() <= 0) {
System.out.println("不予批准");
return;
}
if (leave.getNum() >= this.numStart) {
// 该领导先进行审批
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); // 0~1天
}
@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_ONE, Handler.NUM_THREE); // 1~3天
}
@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_THREE); // 3天+
}
@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);
}
}
在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析:
public interface Request {
}
public interface Response {
}
public interface Filter {
public void doFilter(Request req,Response res,FilterChain c);
}
public class FirstFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
System.out.println("过滤器1 前置处理");
// 先执行所有request再倒序执行所有response
chain.doFilter(request, response);
System.out.println("过滤器1 后置处理");
}
}
public class SecondFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
System.out.println("过滤器2 前置处理");
// 先执行所有request再倒序执行所有response
chain.doFilter(request, response);
System.out.println("过滤器2 后置处理");
}
}
public class FilterChain {
private List<Filter> filters = new ArrayList<Filter>();
private int index = 0;
// 链式调用
public FilterChain addFilter(Filter filter) {
this.filters.add(filter);
return this;
}
public void doFilter(Request request, Response response) {
if (index == filters.size()) {
return;
}
Filter filter = filters.get(index);
index++;
filter.doFilter(request, response, this);
}
}
public class Client {
public static void main(String[] args) {
Request req = null;
Response res = null;
FilterChain filterChain = new FilterChain();
filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());
filterChain.doFilter(req, res);
}
}
【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。
代码如下:
// 电梯接口
public interface ILift {
// 定义四个电梯状态的常量
//public final static int OPENING_STATE = 1;
int OPENING_STATE = 1;
int CLOSING_STATE = 2;
int STOPPING_STATE = 3;
int RUNNING_STATE = 4;
// 设置电梯状态
void setState(int state);
// 电梯操作的功能
void open();
void close();
void run();
void stop();
}
// 电梯类
public class Lift implements ILift {
// 声明记录当前电梯状态的变量
private int state;
@Override
public void setState(int state) {
this.state = state;
}
@Override
public void open() {
switch(state) {
case OPENING_STATE: // 门已经开了,不能再开门了
// do nothing
break;
case CLOSING_STATE: // 关门状态,电梯开门
case STOPPING_STATE: // 电梯停了,电梯开门
System.out.println("电梯开门");
setState(OPENING_STATE);
break;
case RUNNING_STATE: // 运行时电梯不能开门
// do nothing
break;
}
}
@Override
public void close() {
switch (this.state) {
case OPENING_STATE: // 只有开门状态可以关闭电梯门
System.out.println("电梯关门了。。。");
this.setState(CLOSING_STATE);// 关门之后电梯就是关闭状态了
break;
case CLOSING_STATE: // 已经是关门状态,不能关门
// do nothing
break;
case STOPPING_STATE: // 停止时电梯也是关着的,不用关门
// do nothing
break;
case RUNNING_STATE: // 运行时电梯门是关着的,不能关门
// do nothing
break;
}
}
@Override
public void run() {
switch (this.state) {
case OPENING_STATE: // 电梯不能开着门就走
// do nothing
break;
case CLOSING_STATE: // 门关了,可以运行了
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);// 现在是运行状态
break;
case RUNNING_STATE: // 已经是运行状态了
// do nothing
break;
case STOPPING_STATE:
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);
break;
}
}
@Override
public void stop() {
switch (this.state) {
case OPENING_STATE: // 开门的电梯已经是是停止的了(正常情况下)
// do nothing
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:
// do nothing
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(); // 停止
}
}
问题分析:
定义:
对有状态的对象
,把复杂的“判断逻辑”提取到不同的状态对象
中,允许状态对象在其内部状态发生改变时改变其行为
。
状态模式包含以下主要角色。
代码如下:
// 抽象状态类
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 OpeningState extends LiftState {
// 当前状态要执行的方法
@Override
public void open() {
System.out.println("电梯门开启...");
}
@Override
public void close() {
// 修改状态
super.context.setLiftState(Context.CLOSING_STATE);
// 调用当前状态中的context中的close方法
super.context.close();
}
// 电梯门不能开着就跑,这里什么也不做
@Override
public void run() {
//do nothing
}
// 开门状态已经是停止的了
@Override
public void stop() {
//do nothing
}
}
// 电梯关闭状态类
public class ClosingState extends LiftState {
// 电梯门关闭,这是关闭状态要实现的动作
@Override
public void close() {
System.out.println("电梯门关闭...");
}
// 电梯门关了再打开
@Override
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.open();
}
// 电梯门关了就可以运行
@Override
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.run();
}
// 电梯门关着,不按楼层
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
// 电梯运行状态类
public class RunningState extends LiftState {
// 运行的时候不能开电梯门
@Override
public void open() {
//do nothing
}
// 电梯运行时已经关闭电梯门
@Override
public void close() {
//do nothing
}
// 这是在运行状态下要实现的方法
@Override
public void run() {
System.out.println("电梯正在运行...");
}
// 运行的电梯允许停止
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
// 电梯停止状态类
public class StoppingState extends LiftState {
// 停止状态允许开门
@Override
public void open() {
// 修改状态
super.context.setLiftState(Context.OPENING_STATE);
// 动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().open();
}
// 停止状态也可以关门
@Override
public void close() {
// 修改状态
super.context.setLiftState(Context.CLOSING_STATE);
// 动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().close();
}
// 停止状态可以接着运行
@Override
public void run() {
// 修改状态
super.context.setLiftState(Context.RUNNING_STATE);
// 动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().run();
}
// 电梯门停止,这是停止状态要实现的动作
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}
// 环境角色类
public class Context {
// 定义对应状态对象的常量
public static final OpeningState OPENING_STATE = new OpeningState();
public static final ClosingState CLOSING_STATE = new ClosingState();
public static final RunningState RUNNING_STATE = new RunningState();
public static final StoppingState STOPPING_STATE = new StoppingState();
// 定义记录当前电梯状态变量
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
// 设置当前状态对象
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
// 设置当前状态中的Context对象
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();
System.out.println("-------------");
context.setLiftState(new ClosingState());
context.open();
context.run();
context.close();
context.stop();
System.out.println("-------------");
context.setLiftState(new RunningState());
context.open();
context.close();
context.run();
context.stop();
}
}
定义:
又被称为发布-订阅(Publish/Subscribe)模式
,它定义了一种一对多
的依赖关系,让多个观察者对象同时监听某一个主题对象
。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
在观察者模式中有如下角色:
【例】微信公众号
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
代码如下:
定义抽象观察者类,里面定义一个更新的方法
// 抽象观察者类
public interface Observer {
void update(String message);
}
定义具体观察者类,微信用户是观察者,里面实现了更新的方法
// 具体观察者角色类
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);
}
@Override
public String toString() {
return name;
}
}
定义抽象主题类,提供了attach、detach、notify三个方法
// 抽象主题角色
public interface Subject {
// 添加订阅者(观察者)对象
void attach(Observer observer);
// 删除订阅者
void detach(Observer observer);
// 通知订阅者(观察者)更新消息
void notify(String message);
}
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
// 公众号类:具体主题角色类
public class SubscriptionSubject implements Subject {
// 定义一个集合,用来存储多个观察者对象(微信用户)
private List<Observer> weiXinUserList = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
System.out.println(observer + "订阅了公众号");
weiXinUserList.add(observer);
}
@Override
public void detach(Observer observer) {
System.out.println(observer + "取关了公众号");
weiXinUserList.remove(observer);
}
@Override
public void notify(String message) {
// 遍历集合,通知每一个观察者进行更新
for (Observer observer : weiXinUserList) {
observer.update(message);
}
}
}
客户端程序
public class Client {
public static void main(String[] args) {
// 创建公众号(主题)对象
SubscriptionSubject subject = new SubscriptionSubject();
// 创建订阅者
WeiXinUser userA = new WeiXinUser("用户A");
WeiXinUser userB = new WeiXinUser("用户B");
WeiXinUser userC = new WeiXinUser("用户C");
WeiXinUser userD = new WeiXinUser("用户D");
// 订阅公众号
subject.attach(userA);
subject.attach(userB);
subject.attach(userC);
subject.attach(userD);
System.out.println("-------------------");
// 公众号更新,发出消息给订阅者(观察者)对象
subject.notify("公众号更新了!");
System.out.println("-------------------");
// 如果订阅者取消订阅公众号
subject.detach(userD);
subject.notify("公众号更新了!");
}
}
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1,Observable类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
2,Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
【例】警察抓小偷
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:
小偷是一个被观察者,所以需要继承Observable类
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(); // changed = true
super.notifyObservers();
}
}
警察是一个观察者,所以需要让其实现Observer接口
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() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
}
}
客户端代码
public class Client {
public static void main(String[] args) {
// 创建小偷对象
Thief t = new Thief("隔壁老王");
// 创建警察对象
Policemen p = new Policemen("小李");
// 让警察盯着小偷(被观察者小偷的Vector集合中加入观察者警察)
t.addObserver(p);
// 小偷偷东西
t.steal();
}
}
一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。
如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。
定义:
又叫调停模式
,定义一个中介角色
来封装一系列对象之间的交互
,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互
。
中介者模式包含以下主要角色:
【例】租房
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者,租房者和房主属于同事类。
代码如下:
// 抽象中介者类
public abstract class Mediator {
// 联络抽象方法
public abstract void contact(String message, Person person);
}
// 抽象同事类
public abstract class Person {
protected String name; // 租房者或房主的姓名
protected Mediator mediator; // 中介者对象
public Person(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
}
// 房主:具体同事角色类
public class HouseOwner extends Person {
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
// 和中介联系的方法
public void contact(String message) {
// 房主this与中介联系
mediator.contact(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 contact(String message) {
// 租房者this与中介联系
mediator.contact(message, this);
}
// 获取信息
public void getMessage(String message) {
System.out.println("租房者" + name + "收到的信息为:" + message);
}
}
// 房屋中介:具体的中介者角色类
public class MediatorStructure extends Mediator {
// 聚合房主和租房者对象
private HouseOwner houseOwner;
private Tenant tenant;
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
@Override
public void contact(String message, Person person) {
if (person == houseOwner) {
System.out.println("房屋中介接收到房主的消息,向租房者转告中...");
tenant.getMessage(message);
}else {
System.out.println("房屋中介接收到租房者的消息,向房主转告中...");
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.contact(tenant.name + "需要租三室的房子");
// 房主与中介者沟通
houseOwner.contact(houseOwner.name + "现出租一个三室的房屋");
}
}
上面案例中的实质就是将类似图的多对多关系转化成两个一对多关系,降低了租房者和房主的耦合度。
同时提高二者关系的可维护性可拓展性(因为是转化成了一对多的关系)
定义:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
迭代器模式主要包含以下角色:
【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:
代码如下:
定义学生类
// 学生类
public class Student {
private String name; // 姓名
private String number; // 学号
public Student() {
}
public Student(String name, String number) {
this.name = name;
this.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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
}
定义迭代器接口,声明hasNext、next方法
// 抽象迭代器角色接口
public interface StudentIterator {
// 判断是否还有元素
boolean hasNext();
// 获取下一个元素
Student next();
}
定义具体的迭代器类,重写所有的抽象方法
import java.util.List;
// 具体迭代器角色类
public class StudentIteratorImpl implements StudentIterator {
private List<Student> list;
private int position; // 用来记录遍历时的位置
public StudentIteratorImpl(List<Student> list) {
this.list = list;
position = 0;
}
@Override
public boolean hasNext() {
return position < list.size();
}
@Override
public Student next() {
return list.get(position++);
}
}
定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法
// 抽象聚合角色接口
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<>();
@Override
public void addStudent(Student stu) {
list.add(stu);
}
@Override
public void removeStudent(Student stu) {
list.remove(stu);
}
@Override
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"));
// 获取迭代器对象,遍历聚合对象
StudentIterator iterator = aggregate.getStudentIterator();
while (iterator.hasNext()) System.out.println(iterator.next());
}
}
迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明
Iterator
接口的具体迭代器对象具体的来看看 ArrayList的代码实现
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回元素的索引
int lastRet = -1; // 上一个返回元素的索引
int expectedModCount = modCount;
Itr() {}
//判断是否还有元素
public boolean hasNext() {
return cursor != size;
}
//获取下一个元素
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
}
这部分代码还是比较简单,大致就是在 iterator
方法中返回了一个实例化的 Iterator
对象。Itr是一个内部类,它实现了 Iterator
接口并重写了其中的抽象方法。
注意:
当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable
并实现其中的iterator()方法使其返回一个java.util.Iterator
的实现类就可以了。
定义:
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
访问者模式包含以下主要角色:
(Element)
访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。accept
),其意义是指,每一个元素都要可以被访问者访问。Element
),并且可以迭代这些元素,供访问者访问。【例】给宠物喂食
现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。
代码如下:
创建抽象访问者接口
// 抽象访问者角色类
public interface Person {
// 喂食宠物狗
void feed(Cat cat);
// 喂食宠物猫
void feed(Dog dog);
}
创建不同的具体访问者角色(主人和其他人),都需要实现 Person
接口
// 主人:具体访问者角色类
public class Owner implements Person {
@Override
public void feed(Cat cat) {
System.out.println("主人喂食猫");
}
@Override
public void feed(Dog dog) {
System.out.println("主人喂食狗");
}
}
// 其他人:具体访问者角色类
public class Someone implements Person {
@Override
public void feed(Cat cat) {
System.out.println("其他人喂食猫");
}
@Override
public void feed(Dog dog) {
System.out.println("其他人喂食狗");
}
}
定义抽象节点 – 宠物
// 抽象元素角色类
public interface Animal {
// 接受指定访问者访问的功能
void accept(Person person);
}
定义实现Animal
接口的 具体节点(元素)
// 宠物狗:具体元素角色类
public class Dog implements Animal {
@Override
public void accept(Person person) {
person.feed(this); // 访问者给宠物狗喂食
System.out.println("宠物狗:好吃汪");
}
}
// 宠物猫:具体元素角色类
public class Cat implements Animal {
@Override
public void accept(Person person) {
person.feed(this); // 访问者给宠物猫喂食
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 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);
System.out.println("----------------");
// 创建其他人对象
Someone someone = new Someone();
// 让其他人喂食所有的宠物
home.action(someone);
}
}
访问者模式用到了一种双分派
的技术。
① 分派:
变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap()
,map变量的静态类型是 Map
,实际类型是 HashMap
。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。
静态分派(Static Dispatch) 发生在编译时期
,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载
就是静态分派。
动态分派(Dynamic Dispatch) 发生在运行时期
,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。
② 动态分派:
通过方法的重写支持动态分派。
public class Animal {
public void execute() {
System.out.println("Animal");
}
}
public class Dog extends Animal {
@Override
public void execute() {
System.out.println("dog");
}
}
public class Cat extends Animal {
@Override
public void execute() {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Dog();
a.execute();
Animal a1 = new Cat();
a1.execute();
}
}
上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。
Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。
③ 静态分派:
通过方法重载支持静态分派。
public class Animal {
}
public class Dog extends Animal {
}
public class Cat extends Animal {
}
public class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
}
}
这个结果可能出乎一些人的意料了,为什么呢?
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
④ 双分派:
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。
public class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
Execute exe = new Execute();
a.accept(exe);
d.accept(exe);
c.accept(exe);
}
}
在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派
,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。
双分派实现动态绑定的本质:就是在重载方法委派的前面
加上了继承体系
中覆盖
的环节,由于覆盖
是动态
的,所以重载
就是动态
的了。
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
定义:
又叫快照模式
,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
备忘录模式的主要角色如下:
备忘录有两个等效的接口:
- 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
【例】游戏挑战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 RoleStateMemento {
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 class RoleStateCaretaker {
// 声明RoleStateMemento类型的变量
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = 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();
}
}
分析:白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。
备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。
将 RoleStateMemento
设为 GameRole
的内部类,从而将 RoleStateMemento
对象封装在 GameRole
里面;在外面提供一个标识接口 Memento
给 RoleStateCaretaker
及其他对象使用。这样 GameRole
类看到的是 RoleStateMemento
所有的接口,而RoleStateCaretaker
及其他对象看到的仅仅是标识接口 Memento
所暴露出来的接口,从而维护了封装型。类图如下:
代码如下:
窄接口Memento
,这是一个标识接口,因此没有定义出任何的方法
// 备忘录接口:对外提供窄接口
public interface Memento {
}
定义发起人类 GameRole
,并在内部定义备忘录内部类 RoleStateMemento
(该内部类设置为私有的)
// 游戏角色类:发起人角色
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(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;
}
}
}
负责人角色类 RoleStateCaretaker
能够得到的备忘录对象是以 Memento
为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容
// 备忘录对象:管理者对象
public class RoleStateCaretaker {
// 声明Memento接口变量
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.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();
}
}
如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。
// 用于两个整数相加
public static int add(int a,int b){
return a + b;
}
// 用于两个整数相加
public static int add(int a,int b,int c){
return a + b + c;
}
// 用于n个整数相加
public static int add(Integer ... arr) {
int sum = 0;
for (Integer i : arr) {
sum += i;
}
return sum;
}
上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。
显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。
定义:
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和±符号组成的合法序列,“1+3-2” 就是这种语言的句子。
解释器就是要解析出来语句的含义。但是如何描述规则呢?
文法(语法)规则:
文法是用于描述语言的语法结构的形式规则。
expression ::= value | plus | minus
plus ::= expression ‘+’ expression
minus ::= expression ‘-’ expression
value ::= integer
注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。
上面规则描述为 :
表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。
抽象语法树:
在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
用树形来表示符合文法规则的句子。
解释器模式包含以下主要角色。
代码如下:
// 抽象表达式类
public abstract class AbstractExpression {
public abstract int interpret(Context context);
}
// 封装变量的类:终结符表达式角色
public class Variable extends AbstractExpression {
// 声明存储语法中的变量名的成员变量
private String name;
public Variable(String name) {
this.name = name;
}
// 从环境里获取对应的变量数据
@Override
public int interpret(Context context) {
// 直接返回变量的值
return context.getValue(this);
}
@Override
public String toString() {
return name;
}
}
// 加法表达式类:非终结符表达式角色
public class Plus extends AbstractExpression {
private AbstractExpression left; // +号左边的表达式
private AbstractExpression right; // +号右边的表达式
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
// 将左边表达式的结果和右边表达式的结果进行相加
return left.interpret(context) + right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "+" + right.toString() + ")";
}
}
// 减法表达式类:非终结符表达式角色
public class Minus extends AbstractExpression {
private AbstractExpression left; // -号左边的表达式
private AbstractExpression right; // -号右边的表达式
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
// 将左边表达式的结果和右边表达式的结果进行相减
return left.interpret(context) - right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "-" + right.toString() + ")";
}
}
// 环境角色类
public class Context {
// 定义一个Map集合,用来存储变量及对应的值
private Map<Variable, Integer> map = new HashMap<>();
// 添加变量的功能
public void assign(Variable var, Integer value) {
map.put(var, value);
}
// 根据变量获取对应的值
public int getValue(Variable var) {
return map.get(var);
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 创建环境对象
Context context = new Context();
// 创建多个变量对象
Variable a = new Variable("1");
Variable b = new Variable("2");
Variable c = new Variable("3");
Variable d = new Variable("4");
// 将变量存储到环境对象中
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)); // (a-((b-c)+d))
// 解释(计算)
int result = expression.interpret(context);
System.out.println(expression + "=" + result); // (1-((2-3)+4))=-2
}
}