观察者模式
定义:定义对象间 一种一对多的依赖关系,使得单一个对象改变状态,则所有依赖它的对象都会得到通知并被自动更新。
也叫做发布订阅模式。角色如下:
1. Subject被观察者:定义被观察者必须实现的职责,必须能够动态的增加、取消观察者,他一般是抽象类或者是实现类。管理观察者并通知观察者。
2. ConcreteSubject:具体的被观察者,定义被观察者自己的业务逻辑,同时定义对哪些时间进行通知。
3. ConcreteObServer:具体的观察者,各个观察者都有自己的处理逻辑。
通用源码如下:
public abstract class Subject {
private Vector<Observer> obsVector = new Vector<Observer>();
public void addObserver(Observer o){
this.obsVector.add(o);
}
public void delObserver(Observer o){
this.obsVector.remove(o);
}
public void notifyObserver(){
for(Observer o:this.obsVector){
o.update();
}
}
}
public class ConcreteSubject extends Subject {
public void doSomething(){
super.notifyObserver();
}
}
public interface Observer {
public void update();
}
public class ConcreteObserver implements Observer {
public void update() {
System.out.println("接收到消息并进行处理");
}
}
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer obs= new ConcreteObserver();
subject.addObserver(obs);
subject.doSomething();
}
}
- 优点:
- 观察者与被观察者之间是抽象耦合。
- 建立一套触发机制
- 缺点:需要考虑一下开发效率和运行效率问题。一个被观察者,多个观察者,开发和调试就会比较复杂。而在java中消息的通知默认是顺序执行的。一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。
- 使用场景:
- 关联行为场景。关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景
- 跨系统的消息交换场景。如消息队列的处理机制。
注意问题:
- 广播链的问题。一个观察者可以有双重身份,既是观察者也是被观察者。一旦建立这条链,这个逻辑就比较复杂。可维护性非常差。经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次,(传递两次),这还是比较好控制的。
注:它和责任链模式的最大区别就是观察者广播链在传播的过程中消息是随时更改的。它是由相邻的两个节点协商的消息结构;而责任链模式在消息传播过程中基本上保持消息不可变,如果要改变也只是在原有的消息上进行修正。 - 异步处理问题。被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间比较长怎就要用异步,异步处理要考虑线程安全和队列的问题。
- 广播链的问题。一个观察者可以有双重身份,既是观察者也是被观察者。一旦建立这条链,这个逻辑就比较复杂。可维护性非常差。经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次,(传递两次),这还是比较好控制的。
观察者模式的扩展:
java世界中的观察者模式:jDK中已经提供了java.util.Observable实现类和java.util.Observer接口。
项目中真实的观察者模式:对这种模式进行改造或改装。- 观察者的update方法接收两个参数,一个是被观察者,一个是DTO(传输对象)。一般是一个javabean,由观察者消费。
- 观察者响应方式:在一个观察者多个被观察者的情况下,观察者如何快速响应?有两个办法:一是采用多线程技术,及异步架构。二是缓存技术。
- 被观察者尽量自己做主。一般对被观察者的业务逻辑doSomething方法实现重载,如增加一个doSomething(boolean isNotifyObs)方法,决定是否通知观察者,而不是在消息到达观察者时才判断是否要消费。
订阅发布模型
最佳实践:
- 文件系统:在一个牡蛎下新奖励一个文件,这个动作会同时通知目录管理器增加该目录,并通知磁盘管理器减少1KB的空间。
- AMT取钱:当你ka被ATM吞掉,会触发:摄像头连续快拍,通知监控系统吞卡发生,初始化ATM机屏幕返回最初状态。一般前两个动作都是通过观察者模式来完成的,后一个动作是异步完成的。
门面模式
定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
门面模式是外界访问子系统内部的唯一通道。角色如下:
1. Facede门面角色:客户端调用这个角色的方法,此角色知晓子系统的所用功能和责任。一般情况下,本角色会将所有从客户端的情趣委托到相应的子系统去,即该角色没有实际的业务逻辑。只是一个委托类。
2. subsystem子系统角色。类的集合。
通用源码:每一个子系统都不相同,这里使用3个相互无关的类来代表。表示一个子系统的不同处理模块。
public class ClassA{
public void doSomethingA(){
}
}
public class ClassB{
public void doSomethingB(){
}
}
public class ClassC{
public void doSomethingC(){
}
}
public class Facade{
private ClassA a = new ClassA();
private ClassB b = new ClassB();
private ClassC c = new ClassC();
public void methodA(){
this.a.doSomethingA();
}
public void methodB(){
this.b.doSomethingB();
}
public void methodC(){
this.c.doSomethingC();
}
}
- 优点:
- 减少系统的相互依赖。
- 提高了灵活性。
- 提高安全性。想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法休想访问到。
- 缺点:不符合开闭原则,对修改关闭,对扩展开放。
- 使用场景:
- 为一个复杂的模块或子系统提供一个供外界访问的接口。
- 子系统相对独立—外界对子系统的访问只要是黑箱操作即可。
- 预防低水平人员带来的风险扩展。
注意事项:
当满足一下情况时,一个子系统可以有多个门面。
门面已经庞大到不能忍受的程度。可以按照功能拆分为多个门面。
子系统可以提供不同的访问路径。不同的高层模块访问子系统访问权限不同。可以提供不同的门面给不同的高层模块调用。门面不参与子系统的业务逻辑。
备忘录模式
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
角色如下:
1. Originator发起人角色:记录当前时刻的内部状态。负责定义哪些属于备份范围的状态。创建和恢复备忘录数据。
2. Memento备忘录角色:负责存储Originaor发起人对象的内部状态,在需要的时刻提供发起人需要的内部状态。
3. Caretaker备忘录管理员角色:对备忘录进行管理、保存、提供备忘录。
通用源码:
public class Originator {
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento createMemento(){
return new Memento(this.state);
}
public void restoreMemento(Memento _memento){
this.setState(_memento.getState());
}
}
public class Memento {
private String state = "";
public Memento(String _state){
this.state = _state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
public class Caretaker {
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) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.restoreMemento(caretaker.getMemento());
}
}
使用场景:
- 需要保存和恢复数据的相关状态场景。
- 提供一个可回滚的操作。
- 需要监控的副本场景中。例如要监控一个对象的属性,但是监控又不应该作为系统的主要业务来调用,他只是边缘应用。即使出现监控不准,错误报警也影响不大。一般的做法是备份一个主线程的对象,然后由分析程序来分析。
- 数据库连接的事务管理就是用的备忘录模式。
注意事项:
- 备忘录的生命期。备忘录一旦建立就要使用,不使用就立刻删除其引用,等待垃圾回收器对他的回收处理。
- 备忘录的性能。不要在频繁建立备份的场景中使用备忘录模式。
备忘录模式的扩展
1. clone方式的备忘录。发起人角色只要实现Cloneable就可以同时当发起人角色和备忘录角色。并且可以自行管理备忘录。
通用源码:
public class Originator implements Cloneable{
private Originator backup;
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public void createMemento(){
this.backup = this.clone();
}
public void restoreMemento(){
//在进行恢复前应该进行断言,防止空指针
this.setState(this.backup.getState());
}
@Override
protected Originator clone(){
try {
return (Originator)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class Client {
public static void main(String[] args) {
//定义发起人
Originator originator = new Originator();
//建立初始状态
originator.setState("初始状态");
System.out.println("初始状态是"+originator.getState());
//建立备份
originator.createMemento();
//修改状态
originator.setState("修改后的状态...");
System.out.println("修改后的状态是"+originator.getState());
//恢复原有状态
originator.restoreMemento();
System.out.println("恢复后状态是:"+originator.getState());
}
}
使用clone方式的备忘录模式,可以使用在比较简单的场景或者比较单一的场景中。尽量不要与其他的对象产生严重的耦合关系。
多状态的备忘录模式:实现一个javabean对象的所有状态的备份和还原。
public class Originator {
private String state1 = "";
private String state2 = "";
private String state3 = "";
public String getState1() {
return state1;
}
public void setState1(String state1) {
this.state1 = state1;
}
public String getState2() {
return state2;
}
public void setState2(String state2) {
this.state2 = state2;
}
public String getState3() {
return state3;
}
public void setState3(String state3) {
this.state3 = state3;
}
public Memento createMemento(){
return new Memento(BeanUtils.backupProp(this));
}
public void restoreMemento(Memento _memento){
BeanUtils.restoreProp(this, _memento.getStateMap());
}
@Override
public String toString(){
return "state1=" +state1+"\nstat2="+state2+"\nstate3="+state3;
}
}
public class BeanUtils {
public static HashMap<String,Object> backupProp(Object bean){
HashMap<String,Object> result = new HashMap<String,Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor des:descriptors){
String fieldName = des.getName();
Method getter = des.getReadMethod();
Object fieldValue = getter.invoke(bean, new Object[]{});
if(!fieldName.equalsIgnoreCase("class")){
result.put(fieldName, fieldValue);
}
}
} catch (Exception e) {
}
return result;
}
public static void restoreProp(Object bean,HashMap<String,Object> propMap){
try {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor des:descriptors){
String fieldName = des.getName();
if(propMap.containsKey(fieldName)){
Method setter = des.getWriteMethod();
setter.invoke(bean, new Object[]{propMap.get(fieldName)});
}
}
} catch (Exception e) {
System.out.println("shit");
e.printStackTrace();
}
}
}
public class Memento {
private HashMap<String,Object> stateMap;
public Memento(HashMap<String,Object> map){
this.stateMap = map;
}
public HashMap<String,Object> getStateMap() {
return stateMap;
}
public void setStateMap(HashMap<String,Object> stateMap) {
this.stateMap = stateMap;
}
}
public class Caretaker {
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) {
Originator ori = new Originator();
Caretaker caretaker = new Caretaker();
ori.setState1("中国");
ori.setState2("强盛");
ori.setState3("繁荣");
System.out.println("===初始化状态===\n"+ori);
caretaker.setMemento(ori.createMemento());
ori.setState1("软件");
ori.setState2("架构");
ori.setState3("优秀");
System.out.println("\n===修改后的状态===\n"+ori);
ori.restoreMemento(caretaker.getMemento());
System.out.println("\n===恢复后状态===\n"+ori);
}
}
多备份的备忘录:想要恢复到某个时间点的状态。只要修改Caretaker即可。
public class Caretaker {
private HashMap<String,Memento> memMap = new HashMap<String,Memento>();
public Memento getMemento(String idx) {
return memMap.get(idx);
}
public void setMemento(String idx,Memento memento) {
this.memMap.put(idx, memento);
}
}
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
caretaker.setMemento("001",originator.createMemento());
caretaker.setMemento("002",originator.createMemento());
originator.restoreMemento(caretaker.getMemento("001"));
}
}
这种情况注意内存溢出问题。建议增加Map的上限。
在系统管理上,一个备份的数据是不能被修改的。可以使用内部类保证只让发起人访问到。
public class Originator {
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public IMemento createMemento(){
return new Memento(this.state);
}
public void restoreMemento(IMemento _memento){
this.setState(((Memento)_memento).getState());
}
private class Memento implements IMemento{
private String state = "";
private Memento(String _state){
this.state = _state;
}
private String getState() {
return state;
}
private void setState(String state) {
this.state = state;
}
}
}
public interface IMemento {
}
public class Caretaker {
private IMemento memento;
public IMemento getMemento() {
return memento;
}
public void setMemento(IMemento memento) {
this.memento = memento;
}
}
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.restoreMemento(caretaker.getMemento());
}
}