有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。
Observer 模式 : 发送状态变化通知
Observer 即 “观察者”,在 Observer 模式中,当观察对象的状态发生变化时,会通知给观察者。 Observer 模式适用于根据对象状态进行相应处理的场景。
Observer 模式 中出场的角色:
Demo 如下
// 监听者接口
public interface Observer {
void update(NumberGenerator generator);
}
// 监听者实现类
public class NumberObserver implements Observer{
@Override
public void update(NumberGenerator generator) {
System.out.println("number = " + generator.getNumber());
}
}
// 数字生成器
public class NumberGenerator {
private List<Observer> observers = Lists.newArrayList();
@Getter
private int number;
/**
* 执行某个操作
*/
public void execute() {
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < 20; i++) {
number = random.nextInt();
// 通知监听者动作执行
notifyObservers();
}
}
// 添加监听者
public void addObserver(Observer observer) {
observers.add(observer);
}
/**
* 通知所有监听者
*/
public void notifyObservers() {
observers.forEach(observer -> observer.update(this));
}
}
public class ObserverDemoMain {
public static void main(String[] args) {
final NumberGenerator generator = new NumberGenerator();
NumberObserver observer = new NumberObserver();
// 添加监听者
generator.addObserver(observer);
// 执行某个操作
generator.execute();
}
}
诸如 MQ 中的消费者也是订阅了相关的 Queue,当相关的 Queue有消息进入后,会推送给消费者得知,消费者可以处理对应消息。
Spring 中向事务管理器TransactionSynchronizationManager注册事务某个阶段执行的监听事件,如下:
@Transactional(rollbackFor = Exception.class)
@Override
public String testTransactionSynchronization() {
// step1 : 在 sys_role 表中插入一条 role_id = 1 的记录
addRole(1);
// step2 : 在 sys_role 表中插入一条 role_id = 2 的记录
addRole(2);
// 向事务同步管理器注册一个 TransactionSynchronization 匿名实现类,作用是当前事务提交后,执行 addPermission 方法。
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
// 当前事务提交后触发该方法
// step3 : 在 sys_rermission 表中插入一条 permission_id = id 的记录。
@Override
public void afterCommit() {
addPermission(1);
}
});
System.out.println("end");
return "";
}
Spring 中典型的监听器模式:即我们通过自定义 ApplicationEvent 同时通过实现 ApplicationListener 来监听指定事件的发生。如下,在 SpringBoot 启动时会调用如下几个方法,发送容器初始化、容器初始化结束等相关消息,监听对应事件的监听器会监听到该消息并可以做特定的处理。
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑):
扩展思路
相关设计模式:
Mediator 模式:在 Mediator 模式中,有时会使用 Observer 模式来实现 Mediator 角色与 Colleague 角色之间的通信。就“发送状态变化通知”这一点而已,Mediator 模式与 Observer 模式是类似的。不过这两种模式中通知的目的和视角不同。
在Mediator 模式中,虽然也会发送通知,不过那只是为了对 Colleague 角色进行仲裁而已,而在 Observer 模式中,将 Subject 角色的状态变化通知给 Observer 角色的目的则主要是为了使 Subject 角色与 Observer 角色同步。
Memento 模式 :保存对象状态
我们在文本编辑器编辑时,如果不小心误操作,可以通过撤回(undo)功能将内容恢复到之前的状态。而面对对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息,然后再撤销时,还需要根据所保存的信息将实例恢复到原先的状态。
想要恢复实例,需要一个可以自由访问实例内部结构的权限,但是如果稍微不注意又可能会将依赖于实例内部结构的代码分散到编写在程序的各个地方,导致程序变的难以维护。这种情况叫做“破坏了封装性”。
而通过引入表示实例状态的角色,可以在保存和回复实例时有效地方志对象的封装性遭到破坏,即 Memento 模式。
Memento 模式 中出场的角色:
Originator(生成者): Originator角色会在保存自己的最新状态时生成 Memento角色。当把以前保存的 Memento 角色传递给 Originator 角色时,他会将自己恢复至生成该Memento角色时的状态。
Memento(纪念品):Memento 角色会将 Originator 角色的内部信息整合在一起,在Memento角色中虽然保存了 Originator 角色的信息,但是他不会向外部公开这些信息。Memento 角色有以下两种接口:
通过对外提供这两种接口,可由有效防止对象的封装性被破坏。
Caretaker(负责人):当Caretaker 角色想要保存当前的 Originator 角色状态时,会通过 Originator 角色。Originator角色在接收到通知后会生成 Memento 角色的实力并将其返回给 Caretaker 角色。由于以后可能会用 Memento 实例来将 Originator 恢复至原来的状态,所以 Caretaker 角色会一直保存Memento 实例。
不过由于 Caretaker 角色只能使用Memento 角色的两种接口中的窄接口,也就是说它无法访问 Memento 角色内部的所有信息。它只是将 Originator 角色生成的Memento角色当做一个黑盒保存起来。
虽然 Originator 角色和 Memento 角色之前是强关联,但是Caretaker 角色和 Memento 角色之前是弱关联关系。Memento 角色对 Caretaker 角色因此了自身的内部信息。
Demo 如下: 游戏自动进行,通过掷骰子来决定下一个状态,点数为1时增加金钱,2时减少,6时得到水果,而如果金钱减少则通过 Memento 对象回滚到上一轮的状态。
public class Memento {
/**
* 当前持有金钱
*/
private final int money;
/**
* 当前持有水果
*/
private final List<String> fruits = Lists.newArrayList();
public Memento(int money) {
this.money = money;
}
/**
* 获取当前金钱
* @return
*/
public int getMoney() {
return money;
}
/**
* 添加水果
* @param fruit
*/
public void addFruit(String fruit){
fruits.add(fruit);
}
/**
* 获取持有水果
* @return
*/
public List<String> getFruits(){
return fruits;
}
}
public class Gamer {
/**
* 当前持有金钱
*/
private int money;
/**
* 当前持有水鬼
*/
private List<String> fruits = Lists.newArrayList();
/**
* 水果列表
*/
private String[] fruitsName = new String[]{"苹果", "橘子", "香蕉"};
/**
* 随机数
*/
private Random random = new Random(System.currentTimeMillis());
public Gamer(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public List<String> getFruits() {
return fruits;
}
public void bet() {
final int dice = random.nextInt(6) + 1;
if (dice == 1) {
money += 100;
System.out.println("所持有的金钱增加了");
} else if (dice == 2) {
money /= 2;
System.out.println("所持有的金钱减半了");
} else if (dice == 6) {
final String fruit = getFruit();
fruits.add(fruit);
System.out.println("获得了水果 " + fruit);
} else {
System.out.println("什么都没发生");
}
}
/**
* 创建快照
*
* @return
*/
public Memento createMemento() {
Memento memento = new Memento(money);
fruits.stream()
.filter(f -> f.startsWith("好吃的"))
.forEach(memento::addFruit);
return memento;
}
/**
* 快照还原
* @param memento
*/
public void restoreMemento(Memento memento) {
this.money = memento.getMoney();
this.fruits = memento.getFruits();
}
/**
* 获取奖励水果
*
* @return
*/
private String getFruit() {
String prefix = random.nextBoolean() ? "好吃的" : "";
return prefix + fruitsName[random.nextInt(fruitsName.length)];
}
}
public class MementoDemoMain {
public static void main(String[] args) {
Gamer gamer = new Gamer(100);
Memento memento = gamer.createMemento();
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i + ": 当前金钱 = " + gamer.getMoney() + "; 当前水果 = " + gamer.getFruits());
gamer.bet();
System.out.println("当前持有金钱 money = " + gamer.getMoney());
if (gamer.getMoney() > memento.getMoney()) {
System.out.println("当前持有金钱增加, 继续游戏");
memento = gamer.createMemento();
} else {
System.out.println("当前持有金钱减少, 恢复快照状态");
gamer.restoreMemento(memento);
}
}
}
}
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑):
扩展思路
相关设计模式
一时的小想法,仅仅个人理解,无需在意 :
State 模式 :用类表示状态
在面向对象编程中,是用类表示对象的。而在 State 模式中,我们可以用类来表示状态。
State 模式 中出场的角色:
Demo如下 : 一个金库系统,分为白天和晚上两个状态,每小时使用一次金库,同步一次警局。
// 状态接口
public interface State {
/**
* 设置时间
* @param context
* @param hour
*/
void doClock(Context context, int hour);
/**
* 使用金库
* @param context
*/
void doUse(Context context);
/**
* 正常通话
* @param context
*/
void doPhone(Context context);
}
// 白天状态
public class DayState implements State {
private static final DayState INSTANCE = new DayState();
private DayState() {
}
/**
* 单例模式获取实例
*
* @return
*/
public static DayState getInstance() {
return INSTANCE;
}
@Override
public void doClock(Context context, int hour) {
if (hour < 9 || hour >= 17) {
context.changeState(NightState.getInstance());
}
}
@Override
public void doUse(Context context) {
System.out.println("白天使用金库");
}
@Override
public void doPhone(Context context) {
System.out.println("白天正常通话");
}
@Override
public String toString() {
return "白天";
}
}
// 晚上状态
public class NightState implements State {
private static final NightState INSTANCE = new NightState();
private NightState() {
}
/**
* 单例模式获取实例
*
* @return
*/
public static NightState getInstance() {
return INSTANCE;
}
@Override
public void doClock(Context context, int hour) {
if (hour >= 9 && hour < 17) {
context.changeState(DayState.getInstance());
}
}
@Override
public void doUse(Context context) {
System.out.println("晚上使用金库");
}
@Override
public void doPhone(Context context) {
System.out.println("晚上通话录音");
}
@Override
public String toString() {
return "晚上";
}
}
// 上下文
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
/**
* 设置时间
*
* @param hour
*/
public void setClock(int hour) {
System.out.println("现在时间是 : " + hour + ":00");
state.doClock(this, hour);
}
/**
* 改变状态
*
* @param state
*/
public void changeState(State state) {
System.out.println("状态变更 : " + this.state + " -> " + state);
this.state = state;
}
/**
* 使用金库
*
* @param msg
*/
public void doUse(String msg) {
this.state.doUse(this);
}
/**
* 联系报警中心
*
* @param msg
*/
public void callSecurityCenter(String msg) {
this.state.doPhone(this);
}
}
// Main 方法
public class StateDemoMain {
public static void main(String[] args) throws InterruptedException {
// 执行状态,每小时使用一次金库, 拨打警局电话确认安全
Context context = new Context(DayState.getInstance());
for (int i = 0; i < 24; i++) {
context.setClock(i);
Thread.sleep(1000);
context.doUse("");
context.callSecurityCenter(String.valueOf(i));
}
}
}
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑):
在 Memento 模式中举例的项目A中,实际上对于单证的处理不仅仅时修改,还存在多个环节状态的流转,而每个环节状态并非单独字段的判断,因此可以通过 State 模式来管控各个状态之间的流转,并且结合 Memento 模式,可以对每个环节状态做一个快照备份。
@Data
public class BillInfo {
/**
* 表头信息
*/
private String head;
/**
* 表体信息
*/
private List<String> details;
@Override
public String toString() {
return "BillInfo{" +
"head='" + head + '\'' +
", details=" + details +
'}';
}
}
public abstract class BillState {
protected static final MementoContext MEMENTO_CONTEXT = new MementoContext();
/**
* 编辑操作
*
* @param billInfo
*/
public void edit(BillInfo billInfo) {
throw new RuntimeException("not support this operation");
}
/**
* 发送操作
*
* @param billInfo
*/
public void send(BillInfo billInfo) {
throw new RuntimeException("not support this operation");
}
/**
* 删除操作
*
* @param billInfo
*/
public void delete(BillInfo billInfo) {
throw new RuntimeException("not support this operation");
}
}
@Data
public class BillMemento {
/**
* 表头信息
*/
private String head;
/**
* 表体信息
*/
private List<String> details;
/**
* 创建时间
*/
private Date createTime = new Date();
/**
* 快照版本号
*/
private String version;
/**
* 上一个版本号
*/
private String lastVersion;
}
public class DeleteBillState extends BillState {
@Override
public void delete(BillInfo billInfo) {
System.out.println("单证状态已被删除");
}
}
public class SendBillState extends BillState {
@Override
public void send(BillInfo billInfo) {
System.out.println("单证已发送");
}
@Override
public void delete(BillInfo billInfo) {
System.out.println("单证状态无法删除");
}
}
public class EditBillState extends BillState {
@Override
public void edit(BillInfo billInfo) {
System.out.println("单证被编辑");
}
@Override
public void delete(BillInfo billInfo) {
System.out.println("单证被删除");
}
}
public class BillStateProxy {
private static final BillState EDIT_STATE = new EditBillState();
private static final BillState DELETE_STATE = new DeleteBillState();
private static final BillState SEND_STATE = new SendBillState();
/**
* 当前状态
*/
private BillState billState = EDIT_STATE;
/**
* 编辑操作
*
* @param billInfo
*/
public String edit(BillInfo billInfo, String version) {
billState.edit(billInfo);
return MEMENTO_CONTEXT.createMemento(version, billInfo);
}
/**
* 发送操作
*
* @param billInfo
*/
public void send(BillInfo billInfo) {
billState = SEND_STATE;
billState.send(billInfo);
}
/**
* 删除操作
*
* @param billInfo
*/
public void delete(BillInfo billInfo) {
billState = DELETE_STATE;
billState.delete(billInfo);
}
/**
* 快照恢复
* @param billInfo
* @param version
*/
public void restoreBillInfo(BillInfo billInfo, String version){
MEMENTO_CONTEXT.restoreMemento(billInfo, version);
}
}
public class MementoContext {
/**
* 快照存储
*/
private Map<String, BillMemento> store = Maps.newHashMap();
/**
* 创建快照
*
* @param billInfo
* @return
*/
public String createMemento(String laseVersion, BillInfo billInfo) {
BillMemento memento = new BillMemento();
memento.setHead(billInfo.getHead());
memento.setDetails(Lists.newArrayList(billInfo.getDetails()));
memento.setVersion(UUID.randomUUID().toString());
memento.setLastVersion(laseVersion);
storeMemento(memento);
return memento.getVersion();
}
/**
* 快照回滚
*
* @param billInfo
* @param version
*/
public void restoreMemento(BillInfo billInfo, String version) {
final BillMemento memento = getMemento(version);
billInfo.setHead(memento.getHead());
billInfo.setDetails(memento.getDetails());
}
/**
* 存储快照, 可以重写该方法以改变快照的存储方式
*
* @param memento
*/
protected void storeMemento(BillMemento memento) {
store.put(memento.getVersion(), memento);
}
/**
* 获取快照
*
* @param version
*/
protected BillMemento getMemento(String version) {
return store.get(version);
}
}
public class DemoMain {
public static void main(String[] args) {
BillInfo billInfo = new BillInfo();
billInfo.setHead("start");
billInfo.setDetails(Lists.newArrayList());
BillStateProxy billState = new BillStateProxy();
// 快照版本记录
List<String> versions = Lists.newArrayList();
String currVersion = "";
// 单证初始编辑状态,编辑状态不允许发送
for (int i = 0; i < 15; i++) {
billInfo.getDetails()
.add(String.valueOf(i));
currVersion = billState.edit(billInfo, currVersion);
versions.add(currVersion);
}
// 单证发送后变成已发送状态
billState.send(billInfo);
// 已发送状态单证无法删除
billState.delete(billInfo);
System.out.println("操作结束后结果 :" + billInfo);
// 利用快照回滚单证内容
billState.restoreBillInfo(billInfo, versions.get(0));
System.out.println("回滚后结果 :" + billInfo);
}
}
突然想到 播放器的倍速播放似乎可以使用 State 模式?
扩展思路
相关设计模式
https://mp.weixin.qq.com/s/fc8AB6MnYtCyp5jWvVgCjw