赐我白日梦


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

观察者模式

发表于 2019-07-09

在处理类之间的多对一的依赖关系时,观察者设计模式应运而生了,它的出现实现了代码的瘦身,类之间的解耦,本文分三部分:

  • 非观察者模式的多对一的依赖处理
  • 观察者模式对多对一依赖处理的优化
  • Java内置的观察者

假设场景: 前端用户向后端服务器发送不同的请求,后端的Selector区的分不同的请求,回调不同的Handler处理请求, 下面的示例代码

非观察者

当Selector发现数据改变时,想要回调Handler的方法的前提是它要维护一个Handler的引用, 我们在构造函数中传递进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Data
public class Selector {
private String username;
private String password;

private Handler1 currentConditions;

public Selector(Handler1 currentConditions) {
this.currentConditions = new Handler1();
}

public void datachange() {
currentConditions.update(getUsername(),getPassword());
}

// 添加假数据
public void setData(String username,String password){
this.username=username;
this.password=password;
datachange();
}

}

Handkler 的成员变量和Selector一样, 同时真正干活的人是handler, 它里面只有干活的方法以及承载数据的字段, 干活的方法会被回调, 字段由Selector负责初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Handler1 {
private String username;
private String password;

public void update( String username, String password){
this.username=username;
this.password=password;
display();
}

public void display(){
System.out.println("Handler1 : username"+ username);
System.out.println("Handler1 : password"+username);
}
}

测试:

1
2
3
4
5
6
7
public class text {
public static void main(String[] args) {
Handler1 currentConditions = new Handler1();
Selector weatherData = new Selector(currentConditions);
weatherData.setData("张三","123");
}
}

上面的设计方法实现了,当Selector发现新的数据改变时,回调指定的handler的需求,但是缺点很快就暴露出来, Handler引用以构造方法的形式在Selector维护,如果有100个handler, 是不是意味着Selector的构造函数重载100份?


观察者模式

观察者解决了上面的问题,在观察者模式中有两个角色的划分,1.SubJect 2.Observer, 其中 SubJect负责回调Observer提供的方法处理数据, Observer就是观察者,伺机而动, 它之所以能够实现两者的解耦,实现代码的瘦身,精华就是在Subject的构造函数不再维护单一的某一个Observer的引用,而是一个泛型是为Observer类型的集合, 为了防止把非观察者的对象添加到集合中, 故 在原有的基础上,向上进行了一层抽象,把Subject和Observer设计成接口,Subject提供了注册/移除/通知观察者的方法,通知哪个方法呢? 于是Observer接口中提供了需要被回调的抽象方法, 做完这层封装之后,就有了这个观察者体系, 所有的观察者必须实现Observer接口,重写它的方法,任何回调观察者的类,也必须实现Subject,重写它的方法

实例代码:

1
2
3
4
5
6
7
8
9
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}

public interface Observer {
public void update(String m,String p);
}

Subject的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Data
public class Selector implements Subject {
private String username;
private String password;

private ArrayList<Observer> list;

public Selector() {
this.list = new ArrayList<Observer>();
}

// 添加假数据
public void setData( String username, String password){
this.username=username;
this.password=password;

}

@Override
public void registerObserver(Observer o) {
list.add(o);
}

@Override
public void removeObserver(Observer o) {
list.remove(o);
}

@Override
public void notifyObserver() {
list.forEach(s->s.update(getPassword(),getUsername()));
}
}

Observer的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Handler1 implements Observer {
private String username;
private String password;

@Override
public void update( String username,String password){
this.username=username;
this.password=password;
display();
}

public void display(){
System.out.println(" Handler1 "+ username);
System.out.println(" Handler1 "+password);
}
}

测试:

1
2
3
4
5
6
7
8
9
10
public class text {
public static void main(String[] args) {
Handler1 secondConditions = new Handler1();
Selector weatherData = new Selector();
// 注册观察者
weatherData.registerObserver(secondConditions);
weatherData.setData("张三","123");
weatherData.notifyObserver();
}
}

我们自己实现的观察者函数回调的特性: : 虽然实现我们一开始的需求,但是可以看到,数据是在Subject端进行初始化的, 并没有传递到观察端,由观察者自主分配数据


java内置的观察者

java内置的观察中同样分为两种角色,1.Obserable 2.Observer 其中Observer是观察者,同样不变的是,他是个接口

但是: Obserable是一个抽象类, java为我们实现了 注册/移除,通知的方法, 在不允许多继承的java中,抽象类是有缺点的,这使得当前类不能再继承其他类

此外: 相对于我们自定义的观察者, 它做到了把数据传递到 观察者客户端,任由观察者自主的分配这些数据

如何使用:
对于Obserable来说, 继承他

  • 由于java实现了主要的方法, 我们只关注如何使用这几个方法就行了
  • 1: 在回调之前 , 必须先使用 this.setChanged(); 否则信息不会通知到观察者
  • 2: 通知的方法this.notifyObservers(Object obj));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Data
public class Selector extends Observable {
private String username;
private String password;

// 添加假数据
public void setData( String username, String password) {
this.username = username;
this.password = password;
datachange();
}

// 当信息更改, 就推送
public void datachange() {
// todo 加上这一条,当有信息改变时, 就执行
this.setChanged();
// 通知时,把信息同步推送给观察者
this.notifyObservers(new Data(getUsername(),getPassword()));
}

@lombok.Data
public class Data {
private String username;
private String password;

public Data( String username, String password) {
this.username = username;
this.password = password;
}
}
}

观察者: 继承Observer, 重写它被回调的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Handler1 implements Observer {
private String username;
private String password;

@Override
public void update(Observable observable, Object arg0) {
this.password = ((Selector.Data) (arg0)).getPassword();
this.username = ((Selector.Data) (arg0)).getUsername();
display();
}

public void display() {
System.out.println("Handler1 : " + username);
System.out.println("Handler1 : " + password);
}
}

测试

1
2
3
4
5
6
7
8
9
public class text {
public static void main(String[] args) {
Handler1 handler1 = new Handler1();
Selector weatherData = new Selector();
// 先注册的观察者,后执行
weatherData.addObserver(handler1);
weatherData.setData("张三","123");
}
}

NIO编程模型的总结

发表于 2019-06-25

终于,这两天的考试熬过去了, 兴致冲冲的来整理笔记来, 这篇博客是我近几天的NIO印象笔记汇总,记录了对Selector及Selector的重要参数的理解,对Channel的理解,常见的Channel,对NIO事件驱动的编程模型的理解,NIO与传统IO的对比,NIO的TCP/IP编程的实践.

Channel

什么是Channel

这个概念绝对是一级概念,Channel是一个管道,用于连接字节缓冲区和另一端的实体, 这个字节缓冲区就是ByteBuffer, 另一端的实体可以是一个File 或者是 Socket ;

阅读全文 »

NIO零拷贝的深入分析

发表于 2019-06-20

深入分析通过Socket进行数据文件传递中的传统IO的弊端以及NIO的零拷贝实现原理,及用户空间和内核空间的切换方式

阅读全文 »

字符集编码全方位解析

发表于 2019-06-19

是时候了解关于字符集编码了!!!

ASCII

最早计算机刚出世时,只有一ASCII编码方式, American Strand Code For Information Interchange , ASCII 使用7位 标识一个字符, 对于只认识 0 1 的计算机来说, ASCII 可以记录 2^7 个字符, 针对欧美国家来说, abc… 再加上一些运算符号 ,ASCII足够用了

但是问题来了,不光是美国,像法国等国家也普及计算机,很快ASCII就不够了

阅读全文 »

NIO中Buffer的重要属性关系解析

发表于 2019-06-16

Buffer 是java NIO中三个核心概念之一 缓存, 在java的实现体系中Buffer作为顶级抽象类存在

简单说,Buffer在做什么?

我们知道,在java IO中体系中, 因为InputStream和OutputStream是抽象类,而java又不可以多重继承,于是任何一个流要么只读,要么只写.而无法完成同时读写的工作

于是: Buffer来了

NIO中,对数据的读写,都是在Buffer中完成的,也就是说,同一个buffer我们可以先读后写, 它底层维护着一个数组,这个数组被三个重要的属性控制,有机的工作结合,使buffer可读可写;

此外,Buffer是线程不安全的,并发访问需要同步

阅读全文 »

IO流与装饰者模式

发表于 2019-06-14

java使用IO流来处理不同设备之间数据的交互;所有的IO操作实际上都是对 Stream 的操作

从功能上划分:

  • 输入流: 当数据从源进入的编写的程序时,称它为输入流;
  • 输出流: 从程序输出回另一个源成为输出流;

    输入与输出是有参照物的,而这个参照物就是应用程序本身

从结构上划分:

阅读全文 »

实现一个简易的数据库连接池

发表于 2019-06-12

数据库连接池的作用:

1. 资源重用

当多个用户频繁的去对数据库进行读写操作时,会不间断的创建Connection,在数据库开始读写数据之前,把资源过多的分配给创建连接释放连接上,这笔开销得不偿失.数据库连接池的对连接Connection的资源回收机制对此做出了优化

2. 更快的系统响应速度

数据库连接池一旦初始化,用户获取的Connection不再创建新的,而是从现有的容器里面取,使得直接利用成为可能,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。

3. 新的资源分配手段

对于多应用共享同一数据库的系统而言 ,某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。

4. 统一的连接管理,避免数据库连接泄漏

数据库连接池提供连接超时设定,超时后会重试,针对用户获取到的不健康的Connection,同样会重新分配新的连接,从而避免了常规数据库连接操作中可能出现的资源泄漏

阅读全文 »

TCP/IP 协议与 HTTP协议

发表于 2019-06-07

不得不提的TCP/IP协议,TCP/IP不是两个协议,而是一整个协议栈;

TCP/IP包含如下图四层:其中每一层都使用着一个或者多个协议,遵循这些协议就可以实现不同设备之间的轻松通信

  • 应用层(Http协议 DNS协议 Email协议…)
  • 传输层(TCP协议 UDP协议)
  • 网络层 (IP协议 )
  • 网络接口层(设备的驱动程序+接口卡)

其中TCP(transfer control protocol)协议是传输层的协议,他是面向连接的,基于字节流的传输通信协议, Java对Tcp的支持体现在它的网络编程包中的Socket与ServerSocket的封装.

阅读全文 »

腾讯短信+Springboot+Redis 实现短信验证注册

发表于 2019-05-25 | 分类于 java

使用redis做缓存实现用户的注册功能:

  • 异步请求发送短信,给 发送短信的按钮 绑定异步事件
    • 调用发送短信逻辑发送短信
      • 缓存 key1:验证码
      • 缓存 key2:短信发送时刻的时间
  • 用户提交表单 包含用户的基本信息+验证码
    • 取出用户的验证码去redis中查找
      • 若不存在返回异常
      • 未过期,直接退出发短信的方法
      • 存在根据key1取出验证码,和用户提交的比对,相同继续注册,否则返回异常
阅读全文 »

ubuntu16搭建文件服务器

发表于 2019-05-15

这篇记录,如何在ubuntu16 安装 FastDFS 文件服务器,详细步骤

阅读全文 »
12…6
赐我白日梦

赐我白日梦

随笔

52 日志
19 分类
22 标签
© 2019 赐我白日梦
本站访客数: