人物:大鸟,小菜
事件:小菜给大鸟讲了他公司一个有趣的事情,今天上班时老板出去了,然后大家都在关注股票的事,平时老板回来的时候,前台美女秘书童子喆都会给大家打电话以作提醒,但今天老板回来的时候,叫童子喆去办了点事,然后和老板一起进的公司,所以小秘书没机会打电话提醒,这时老板就看到了公司全员炒股的盛况,大鸟说,你这不就是观察者模式嘛
观察者模式:
1.结合股票事件初次实现观察者模式
2.对实现的观察者模式进行优化,解耦一:抽象观察者
3.进一步进行解耦,抽象通知者接口
4.了解观察者模式怎么进行委托,更贴近实际
大鸟:这是一个典型的观察者模式,你可以将此事写成代码记录一下
小菜将股票事件结合观察者模式的第一次实现
前台秘书类:
@Data public class Secretary { private List<StockObserver> observers = new ArrayList<>(); private String action; /** * 增加:有几个同事请前台小姐姐帮忙,就加入几个 * * @param observer */ public void attach(StockObserver observer) { observers.add(observer); } /** * 通知:之前有几个加入的,就给几个发信息提醒 */ public void notifyMembers() { for (StockObserver stockObserver : observers) { stockObserver.update(); } } /** * 前台状态:前台通过电话做的事 */ public void secretaryAction(String action) { this.action = action; } }
正在看股票的同事类:
@Slf4j public class StockObserver { private String name; private Secretary sub; public StockObserver(String name, Secretary sub) { this.name = name; this.sub = sub; } public void update() { log.info("{}, {}关闭股票详情,继续工作!", sub.getAction(), name); } }
客户端程序类:
public static void main(String[] args) { Secretary tongzizhe = new Secretary(); StockObserver tongshi1 = new StockObserver("同事甲", tongzizhe); StockObserver tongshi2 = new StockObserver("同事乙", tongzizhe); tongzizhe.attach(tongshi1); tongzizhe.attach(tongshi2); tongzizhe.secretaryAction("老板回来了!"); tongzizhe.notifyMembers(); }
运行结果:
老板回来了!, 同事甲关闭股票详情,继续工作!
老板回来了!, 同事乙关闭股票详情,继续工作!
大鸟:
不错,大致是实现了,但是你有没发现,看股票的同事类和前台类有部分是相互耦合的,在看股票的同事类里,还需要获得,前台类的前台状态;而前台类里的通知方法,也需要调用
看股票同事类的update()方法。
比如,现在前台类调用update()方法去通知时,之前是固定通知关闭股票详情,但有人想看NBA的网络直播,那前台类又该怎么办呢?小菜,设计之前要记住先遵守开放封闭原则,要修改原有代码说明设计不够好,然后还要遵循依赖倒转原则,让程序都依赖于抽象
小菜解耦后的实现一
增加了抽象的观察者:
public abstract class Observer { protected String name; protected Secretary sub; public Observer(String name, Secretary sub) { this.name = name; this.sub = sub; } public abstract void update(); }
增加了两个具体的观察者,看股票的,和看NBA的:
@Slf4j public class StockObserver extends Observer { public StockObserver(String name, Secretary sub) { super(name, sub); } @Override public void update() { log.info("{}, {}关闭股票详情,继续工作!", sub.getAction(), name); } }
@Slf4j public class NBAObserver extends Observer { public NBAObserver(String name, Secretary sub) { super(name, sub); } @Override public void update() { log.info("{}, {}关闭NBA,继续工作!", sub.getAction(), name); } }
然后将前台秘书类里具体的观察者换成抽象的观察者
@Data public class Secretary { /** * 同事列表 */ private List<Observer> observers = new ArrayList<>(); private String action; /** * 增加 * * @param observer */ public void attach(Observer observer) { observers.add(observer); } /** * 通知 */ public void notifyMembers() { for (Observer stockObserver : observers) { stockObserver.update(); } } public void removeMembers(Observer observer) { observers.remove(observer); } /** * 前台状态 */ public void secretaryAction(String action) { this.action = action; } }
客户端代码:
public static void main(String[] args) { Secretary tongzizhe = new Secretary(); StockObserver tongshi1 = new StockObserver("同事甲", tongzizhe); StockObserver tongshi2 = new StockObserver("同事乙", tongzizhe); NBAObserver tongshi3 = new NBAObserver("同事丙", tongzizhe); tongzizhe.attach(tongshi1); tongzizhe.attach(tongshi2); tongzizhe.attach(tongshi3); tongzizhe.secretaryAction("老板回来了!"); tongzizhe.notifyMembers(); }
实现结果:
老板回来了!, 同事甲关闭股票详情,继续工作! 老板回来了!, 同事乙关闭股票详情,继续工作! 老板回来了!, 同事丙关闭NBA,继续工作!
大鸟:小菜,你这样只完成了一半,你虽然已经抽象了一个观察者,但是前台秘书也是一个具体的类,如果一个前台秘书被老板叫去做事了,那么通知的事是不是就应该交给另一个同事做了?还有,还应该再增加一个功能,如果秘书和一个同事有矛盾,是不是就应该将那个同事给删除不通知了?
小菜解耦后的实现二
抽象了通知者接口
public interface Subject { /** * 注册 * * @param observer */ void attach(Observer observer); /** * 删除 * * @param observer */ void detach(Observer observer); /** * 通知 */ void notifyMembers(); /** * 存入前台要通知的状态 * * @param action */ void setAction(String action); /** * 获取前台状态 * * @return */ String getAction(); }
因为通知者类可能是前台可能是老板,所以老板也要去实现通知接口:
public class Boss implements Subject { private List<Observer> observers = new ArrayList<>(); private String action; @Override public void attach(Observer observer) { observers.add(observer); } @Override public void detach(Observer observer) { observers.remove(observer); } @Override public void notifyMembers() { for (Observer o : observers) { o.update(); } } @Override public void setAction(String action) { this.action = action; } @Override public String getAction() { return action; } }
抽象观察者:
public abstract class Observer { protected String name; protected Subject sub;
//原来是前台类:SecretaryImpl,现改为抽象通知者 public Observer(String name, Subject sub) { this.name = name; this.sub = sub; } public abstract void update(); }
看股票的同事和看NBA的同事:
@Slf4j public class StockObserver extends Observer { public StockObserver(String name, Subject sub) { super(name, sub); } @Override public void update() { log.info("{}, {}关闭股票详情,继续工作!", sub.getAction(), name); } }
@Slf4j public class NBAObserver extends Observer { public NBAObserver(String name, Subject sub) { super(name, sub); } @Override public void update() { log.info("{}, {}关闭NBA,继续工作!", sub.getAction(), name); } }
客户端代码:
public static void main(String[] args) { Boss boss = new Boss(); StockObserver tongshi1 = new StockObserver("贾", boss); NBAObserver tongshi2 = new NBAObserver("易", boss); boss.attach(tongshi1); boss.attach(tongshi2); boss.detach(tongshi1); boss.setAction("老板我回来了"); boss.notifyMembers(); }
观察者模式代码结构图
观察者模式
1.概念:观察者模式定义了一种一对多的依赖关系,让多个观察对象同时监听某一个主题对象,这个主体对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更细自己
2.使用观察者模式的动机是什么?
答:当一个系统分割成一系列互相协作的类时,有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了一致性而导致各类紧密耦合,这样会给维护,扩展,重用都带来不便。这时观察者模式就起到了很好地作用,
3.什么时候用观察者模式?
答:当一个对象的改变同时需要通知其他对象,而且不一定知道有多少对象需要改变时,应考虑使用观察者模式
4.补充:一个抽象模型有两个方面,其中一方面依赖于另一个方面,这时观察者模式可以将两者封装在独立的对象中使它们各自独立地改变和复用。总的来讲,观察者模式所做的工作其实就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一方的变化
事件委托实现
思考:上述的观察者抽象能抽象成接口么,小菜可能觉得看股票的人和看NBA的人他们的类都是相似的,所以用了抽象类,可以共用一些代码,但在实际使用过程中观察者可能是完成不同的两个类,但这时需要他们都做出update()操作,这时只需要实现这个接口就行了:
public interface Observer { void update(); }
尽管已经使用了依赖倒转原则,但是抽象通知者依旧是依赖抽象观察者,也就是说如果没有抽象观察者这样的接口,通知的功能就无法实现。此外就是每个具体观察者不一定是更新的方法要调用。
由此引出事件委托:
其实委托的思想就是,本来是通过update()就可以通知一个具体的事情,如:关闭NBA直播,用了委托后,一个update()就可以通知几个事情,比如观察者调用update()方法,可以通知关闭NBA直播,关闭股票页面等操作
概念:委托就是一种引用方法的类型,一旦为委托分配了方法,委托将于该方法具有完全相同的行为。委托方法的使用可以和其他方法一样,具有参数和返回值。委托可以看成是对方法的抽象。