LieBrother

当才华撑不起野心时,应该静下心来学习;当能力驾驭不了目标时,应该沉下心来历练。


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

行为型模式:备忘录模式

发表于 2019-03-27   |     |   阅读次数

海

十一大行为型模式之九:备忘录模式。

简介

姓名 :备忘录模式
英文名 :Memento Pattern
价值观 :凡事要有备份
个人介绍 :
Without violating encapsulation,capture and externalize an object’s internal state so that the object can be restored to this state later.
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
(来自《设计模式之禅》)

你要的故事

点开看这篇文章的各位,都是。。。程序界的大佬。作为程序猿,免不了『上线』这件小事。每逢上线必祭天。。。上线这件事我们很多人都操作过,每家公司有不同的上线流程以及上线的技术能力。按照发布的平台的完善程度大概分为 3 种。

  1. 发布平台牛逼的公司:只需要按下『一键部署』按钮,就搞定上线,按下『回滚』按钮,就搞定回滚上一个版本。
  2. 发布平台稍差点的公司:可能就得多个步骤操作了,上线:备份并关闭应用、部署并启动新应用;回滚:关闭新应用、恢复旧应用并启动。
  3. 没有发布平台的公司:那就全程手工操作,上线:关闭旧应用、复制旧应用到备份空间、复制新应用到部署环境、启动新应用;回滚:关闭新应用、删除新应用、从备份空间复制旧应用到部署环境、启动旧应用。

其实通过发布平台完善程度可以侧面反映企业的技术成熟程度。怎么说呢?发布系统的操作难易程度在我们作为程序猿心中,都有一个可接受的范围。假设刚开始是单体应用,一个 tomcat 和一个 war 就搞定,你需要发布平台么?并不需要,咱 ctrl+C、ctrl+V、shutdown、startup 就行,还弄什么发布平台。当系统有多套服务时,每次上线都需要部署 10 个机器的应用,这时你能忍么?要是还是手工操作,那发布一次系统得花费好长时间,要是再搞不好,回滚一次,一晚上都没了;这时就会逼迫开发出一个简易的发布平台,把对多台机器的操作步骤放到发布平台上。当系统是以微服务的架构发展时,每个服务都有上百个实例,那这时就不能简单的把操作步骤搬到发布平台了,还得简化步骤,最终变成上面说的 一键部署 和一键回滚。

上面的 3 种我都亲身经历过。。。在刚出来实习时候,就经历了第 3 种情况,因为是单体应用,一个应用搞定所有东西,手动部署已满足要求。到了银行工作,接触到了云平台,那时就只需要一个按钮就唰唰唰的部署了,也是上面说的第 1 种。而现在,正在经历第 2 种发布平台,只是简单的把操作步骤搬到了系统上,目前的情况是机器越来越多,操作步骤没删减的话,每次发布会花费很多时间,这也会去促进开发出更方便使用的发布平台。

回到今天的主题,今天讲的是备忘录模式,从字面上理解,就是讲备份东西,有了备份就可以恢复。上面讲了一大堆发布的东西,也是咱们工作中接触蛮多的事情,发布的最核心就是要支持部署新应用以及回滚老应用,回滚特别重要,它能够保证在新应用出现异常的情况下,马上恢复到旧应用可用的状态,减少异常的影响面。发布这东东也很符合备忘录模式,下面通过模拟发布步骤代码来讲备忘录模式。这里讲的不是上面说的第 1 种,这里围绕着第 3 种发布步骤写,不管哪种发布平台,它们的底层都是一样的。

先定义应用实例这个类,应用一般会有应用名、版本号信息。

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
34
35
36
/**
* 应用实例
*/
class App {
private String content;
private String version;

public App(String content, String version) {
this.content = content;
this.version = version;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getVersion() {
return version;
}

public void setVersion(String version) {
this.version = version;
}

@Override
public String toString() {
return "App{" +
"content='" + content + '\'' +
", version='" + version + '\'' +
'}';
}
}

定义 AppBackup 来充当备忘录角色,它有一个属性就是 App,也就是备份的应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 应用备份(充当备忘录角色)
*/
class AppBackup {

private App app;

public AppBackup(App app) {
this.app = app;
}

public App getApp() {
return app;
}

public void setApp(App app) {
this.app = app;
}
}

有了备忘录,也需要一个空间来存放备忘录,并对外提供备忘录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 备份空间
*/
class Space {
private AppBackup appBackup;

public AppBackup getAppBackup() {
return appBackup;
}

public void setAppBackup(AppBackup appBackup) {
this.appBackup = appBackup;
}
}

有了这些备份机制,还需要有一个程序猿来部署,这位同学需要掌握发布步骤的所有过程,部署新应用以及回滚旧应用。

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
34
35
36
37
38
39
40
41
42
/**
* 部署应用的同学
*/
class Deployer {

// 要部署的应用
private App app;

public App getApp() {
return app;
}

// 设置部署应用
public void setApp(App app) {
this.app = app;
}

// 创建应用的备份
public AppBackup createAppBackup() {
return new AppBackup(app);
}

// 从备忘录恢复应用
public void setAppBackup(AppBackup appBackup) {
this.app = appBackup.getApp();
}

// 显示应用的信息
public void showApp() {
System.out.println(this.app.toString());
}

// 暂停应用
public void stopApp() {
System.out.println("暂停应用:" + this.app.toString());
}

// 启动应用
public void startApp() {
System.out.println("启动应用:" + this.app.toString());
}
}

再献上测试代码。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class MementoTest {

public static void main(String[] args) {
Deployer deployer = new Deployer();
deployer.setApp(new App("apply-system", "1.0.0"));

System.out.println("1. 暂停旧应用");
deployer.stopApp();

System.out.println("2. 备份旧应用");
Space space = new Space();
space.setAppBackup(deployer.createAppBackup());

System.out.println("3. 拷贝新应用到服务器");
deployer.setApp(new App("apply-system", "2.0.0"));
deployer.showApp();

System.out.println("4. 启动新应用");
deployer.startApp();

System.out.println("5. 有异常,暂停新应用");
deployer.stopApp();

System.out.println("6. 回滚旧应用,拷贝备份的旧应用到服务器");
deployer.setAppBackup(space.getAppBackup());
deployer.showApp();

System.out.println("7. 启动备份的旧应用");
deployer.startApp();
}

}

打印结果:
1. 暂停旧应用
暂停应用:App{content='apply-system', version='1.0.0'}
2. 备份旧应用
3. 拷贝新应用到服务器
App{content='apply-system', version='2.0.0'}
4. 启动新应用
启动应用:App{content='apply-system', version='2.0.0'}
5. 有异常,暂停新应用
暂停应用:App{content='apply-system', version='2.0.0'}
6. 回滚旧应用,拷贝备份的旧应用到服务器
App{content='apply-system', version='1.0.0'}
7. 启动备份的旧应用
启动应用:App{content='apply-system', version='1.0.0'}

备忘录模式代码实现搞定。有同学会不会觉得挺麻烦的,为什么要有AppBackup?我们看看个人介绍,在对象之外保存状态,AppBackup 就是对象之外的对象,用来保存旧应用。

总结

备忘录模式定义了一个备份机制。在很多场景都有类似备忘录模式的实现,比如数据库的事务的回滚机制。在平常业务开发中并没有经常使用这个设计模式,但是我们有使用它的思想,比如我们用数据库或者其他中间件做备份数据,其中备份思想是一致的。

推荐阅读

行为型模式:状态模式

行为型模式:观察者模式

行为型模式:迭代器模式

设计模式系列文章持续更新中,欢迎关注公众号,一起交流学习。

LieBrother

复盘一次生产问题

发表于 2019-03-25   |     |   阅读次数

夜空

有整整 10 天木有更文了,这段时间确实比较忙。

有加我微信的朋友知道我上周末出去春游了,部门组织去了趟外伶仃岛,环境挺不错的,这段时间去的人也比较少,值得去玩。

今天讲讲上周末一次生产问题的复盘。

1 事情经过

周日中午从外伶仃岛回来就直奔公司,因为生产出了些问题。问题是这样的:HBase 的一些节点挂了,导致一些数据丢失。丢失数据的客户来授信或者借款,都会卡件。在确定数据短时间没法恢复时,就决定从系统的层面去解决这个问题。这时我咨询了 2 位老员工,这些数据虽然是规则的入参数据,但是规则可能没用这些数据去做决策,能否先跟规则的同事确认这些数据是否有使用,如果没有,就可以先暂停这些数据的获取,减少影响面,再来细致的分析数据。得到的回复都是这些数据很早前就上线了,肯定有在用。这时只能分析系统数据,恰巧丢失的数据是原始数据,不是加工数据,原始数据不做规则入参,所以就简单的修改了获取数据源的代码。

在测试同事进行简单回归测试时,发现了一个奇怪的现象,旧数据被覆盖,检查了各种 SQL 配置,没有发现问题,因为以前也有很多模型和规则入参都是这样配置的,接着就陷入历史问题的 debug 中,还是没有发现问题所在,到了晚上快 11 点,有同事联系了规则同事,才发现卡件的数据他们并没有在借款的规则中使用,也就是可以通过关闭获取数据源来解决借款卡件问题,作罢,先解决数据卡件问题,后面再细致分析历史问题,搞完回到家 1 点半。

2 复盘

这周也是持续在跟进这个生产历史问题,最终发现是系统框架的 Bug,在数据处理的时候,私有的数据被公共的数据覆盖导致的。这段时间也一直在思考这次生产问题,从马后炮来说,其实可以很快就把卡件问题解决,但是其中却经历了整整 10 个小时的折腾,肯定是有原因的,通过这篇文章复盘一下。

2.1 惯性思维

从维基百科上看这个定义:惯性思维(Inertial thinking)指人习惯性地因循以前的思路思考问题,仿佛物体运动的惯性。惯性思维常会造成思考事情时有些盲点,且缺少创新或改变的可能性。

上面的过程发现了 2 处惯性思维。

  1. 一处是同事们因为经历了整个系统的开发过程,所以直接否定了确认规则是否有在使用丢失的数据的方案;而我因为没有经历前程的开发,算是一个旁观者去看待这个问题,所以才有这个想法先确定数据有没在使用。这里的惯性思维是:因为数据很早前就上线了,当时就在使用,所以现在数据还在使用。
  2. 另外一处则是我对待生产出现的历史问题,一直在通过检查业务代码和 SQL 配置去尝试解决这个问题,因为以前也是这样使用的,以前没出现问题。这里的惯性思维是:以前这样使用没问题,这一次有问题应该是业务代码或者 SQL 配置有问题。

这里都是因为以前做过某些事情是没错的,导致在遇到相同问题的时候会去把以前没错的做法当成是正确答案,而其实没错不等于正确,以前没错的做法只是参考答案,不是正确答案,这里就涉及到思维问题,如果当成参考答案,那么思维是发散的,这个参考答案觉得不对则可以再找其他参考答案或者去发现其他解决方案;如果当成正确答案,那么思维是僵化的,会把这个正确答案一直往里套,就会走不出来。

理解了上面这点,那有什么可以去摆脱惯性思维呢?下面这两点不确定是不是对的,但是是我通过思考,决定接下来要尝试去执行的。

  1. 告诉自己,这是惯性思维。《正念的奇迹》书中讲过洗碗、吃橘子的案例,都是去感受洗碗、去感受吃橘子的感觉。有健身的朋友也会知道,健身肌肉酸痛的时候,去感受那个感觉。让自己去清晰的正面对待惯性思维,而不是去埋怨自己怎么又陷入惯性思维,正面对待它,然后告诉自己,这是惯性思维,这个参考答案是错的,找另一个答案。
  2. 空杯状态。如果没有好的参考答案,放空自己,根据眼前看到的事情,按正常的解决思路去解决。

2.2 明确轻重缓急

当时最紧要的事情是解决生产卡件的问题。在解决的过程中,却发现了一个历史遗留的 Bug,这时卡件的问题代码已经验证通过了,应该直接就上生产,解决当前的燃眉之急,再解决历史遗留的 Bug。现实是一直去把心思放到历史遗留的 Bug 中,导致延迟了很久才把 hotfix 上线。

明确轻重缓急很重要,不仅在特殊紧急的情况,在平时工作中也是一样重要,每天要做的事情很多,要学会先做什么,后做什么。解决这个问题,可以采用四象限工作法,什么是四象限工作法?看下图。

四象限工作法
(注:这里的象限划分和数学上的有些差别,数学上三、四象限和图上是相反的,这里是按照事情的重要紧急程度排序)

每件工作用 2 个维度去衡量,分别是重要性和紧急度。按照这 2 个维度进行分析后落位到各个象限,然后根据象限的顺序去执行,比如上面遇到的卡件以及历史 Bug 问题,卡件这个是放在一象限,历史 Bug 是放在二象限,所以应该分开解决,解决好了卡件问题之后,再去解决历史 Bug。当时有这个意识的话,在验证卡件 hotfix 代码没问题后,就可以直接上线了,后续再分析历史 Bug。

3 总结

经过这次事情,让自己静下心来思考,思考哪些地方做错了,思考犯错的本质,思考如何去避免再犯同样的错,思考怎么去用实际的行动改进。犯错不可怕,可怕的是一错再错。嗯,这一刻,我又成长了。希望我的复盘也能给你一些启示。

推荐阅读:

行为型模式:观察者模式

行为型模式:迭代器模式

行为型模式:策略模式

LieBrother

行为型模式:状态模式

发表于 2019-03-14   |     |   阅读次数

景色

十一大行为型模式之八:状态模式。

简介

姓名 :状态模式
英文名 :State Pattern
价值观 :有啥事让状态我来维护
个人介绍 :
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
(来自《设计模式之禅》)

你要的故事

现在有好多个人贷款软件,比如:支付宝、360借条(打广告。。。)等等。贷款会有一个用户状态流程,游客->注册用户->授信用户->借款用户(这里简化了状态,只用 4 个)。每个状态拥有的权限不一样,如下图所示。

状态

从上图可以看到,一个用户有 3 种行为,分别是注册、授信、借款。当注册成功后,用户的状态就从『游客』改变为『注册用户』;当授信成功后,用户的状态就从『注册用户』改变为『授信用户』;当借款成功后,用户的状态就从『授信用户』改变为『借款用户』。现在我们就来实现用户注册、授信、借款的过程,因为每个状态的权限不一样,所以这里需要根据用户的状态来限制用户行为。

很快,我们就完成下面的代码。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class User {
private String state;

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public void register() {
if ("none".equals(state)) {
System.out.println("游客。注册中。。。");
}else if ("register".equals(state)) {
System.out.println("注册用户。不需要再注册。");
} else if ("apply".equals(state)) {
System.out.println("授信用户。不需要再注册。");
} else if ("draw".equals(state)) {
System.out.println("借款用户。不需要再注册。");
}
}

public void apply() {
if ("none".equals(state)) {
System.out.println("游客。不能申请授信。");
}else if ("register".equals(state)) {
System.out.println("注册用户。授信申请中。。。");
} else if ("apply".equals(state)) {
System.out.println("授信用户。不需要再授信。");
} else if ("draw".equals(state)) {
System.out.println("借款用户。不需要再授信。");
}
}

public void draw(double money) {
if ("none".equals(state)) {
System.out.println("游客。申请借款【" + money + "】元。不能申请借款。");
} else if ("register".equals(state)) {
System.out.println("注册用户。申请借款【" + money + "】元。还没授信,不能借款。");
} else if ("apply".equals(state)) {
System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");
} else if ("draw".equals(state)) {
System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");
}
}
}

public class NoStateTest {

public static void main(String[] args) {
User user = new User();
user.setState("register");
user.draw(1000);
}

}

打印结果:
注册用户。申请借款【1000.0】元。还没授信,不能借款。

上面代码实现了用户 register (注册),apply (授信),draw (借款) 这 3 种行为,每个行为都会根据状态 state 来做权限控制。看起来有点繁琐,扩展性不高,假设新增了一个状态,那么注册、授信、借款这 3 种行为的代码都要修改。下面通过状态模式来解决这个问题。

我们把状态给抽出来,作为一个接口,因为在每种状态中都可能有注册、授信、借款行为,所以把这 3 个行为作为状态接口的方法,让每个状态子类都实现相应的行为控制。如下代码所示。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
interface State {

void register();

void apply();

void draw(double money);
}

/**
* 游客
*/
class NoneState implements State {

@Override
public void register() {
System.out.println("游客。注册中。。。");
}

@Override
public void apply() {
System.out.println("游客。不能申请授信。");
}

@Override
public void draw(double money) {
System.out.println("游客。申请借款【" + money + "】元。不能申请借款。");
}
}

/**
* 注册状态
*/
class RegisterState implements State {

@Override
public void register() {
System.out.println("注册用户。不需要再注册。");
}

@Override
public void apply() {
System.out.println("注册用户。授信申请中。。。");
}

@Override
public void draw(double money) {
System.out.println("注册用户。申请借款【" + money + "】元。还没授信,不能借款。");
}
}

/**
* 授信状态
*/
class ApplyState implements State {

@Override
public void register() {
System.out.println("授信用户。不需要再注册。");
}

@Override
public void apply() {
System.out.println("授信用户。不需要再授信。");
}

@Override
public void draw(double money) {
System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");
}
}

/**
* 借款状态
*/
class DrawState implements State {

@Override
public void register() {
System.out.println("借款用户。不需要再注册。");
}

@Override
public void apply() {
System.out.println("借款用户。不需要再授信。");
}

@Override
public void draw(double money) {
System.out.println("申请借款【" + money + "】元。申请借款中。。。");
}
}

class User1 {
private State state;

public State getState() {
return state;
}

public void setState(State state) {
this.state = state;
}

public void register() {
this.state.register();
}

public void apply() {
this.state.apply();
}

public void draw(double money) {
this.state.draw(money);
}
}

public class StateTest {
public static void main(String[] args) {
User1 user1 = new User1();
user1.setState(new RegisterState());
user1.apply();
user1.draw(1000);
user1.setState(new ApplyState());
user1.draw(2000);
}

}


打印结果:
注册用户。授信申请中。。。
注册用户。申请借款【1000.0】元。还没授信,不能借款。
授信用户。申请借款【2000.0】元。申请借款中。。。

看上面代码,我们抽象了 State 接口,4 种状态分别用 NoneState (游客)、RegisterState (注册)、ApplyState (授信)、DrawState (借款) 表示。而每个状态都有 3 种行为,它们各自对这些行为进行权限控制。这样子实现可以让权限逻辑分离开,分散到每个状态里面去,如果以后要业务扩展,要新增状态,那就很方便了,只需要再实现一个状态类就可以,不会影响到其他代码。这也是为什么《阿里巴巴 Java 开发手册》里面讲的,当超过 3 层的 if-else 的逻辑判断代码,推荐用状态模式来重构代码。

总结

状态模式 很好的减低了代码的复杂性,从而提高了系统的可维护性。在业务开发中可以尝试使用,比如在迭代开发中,业务逻辑越来越复杂,从而不得不使用很多 if-else 语句来实现时,就可以考虑一下是不是可以用 状态模式 来重构,特别是一些有状态流程转换方面的业务。看到这篇文章,想想工作中是不是有些复杂的代码可以重构,赶紧行动起来。

推荐阅读:

行为型模式:观察者模式
行为型模式:迭代器模式
行为型模式:策略模式

设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。

LieBrother

行为型模式:观察者模式

发表于 2019-03-11   |     |   阅读次数

灯塔

十一大行为型模式之七:观察者模式。

简介

姓名 :观察者模式
英文名 :Observer Pattern
价值观 :盯着你怎么着
个人介绍 :
Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
(来自《设计模式之禅》)

你要的故事

想来想去,就拿我们现在生活中最常体会到的事情来讲观察者模式–朋友圈。小明、小红、小东 3 人是好朋友,最近他们的父母都给安排了手机,刚用上手机那是相当的兴奋呀。他们立马从 QQ 转投到微信的怀抱,对微信的朋友圈玩的不亦乐乎,什么事情都往上面发。突然有一天,小明和小红因为一些小事争执闹别扭了,原因就是他们对一道数学题有不同的见解。就跟我们小时候和朋友玩得好好的,突然因为一点小事就闹翻了。小红比较孩子气,立马就屏蔽了小明的朋友圈,不想再看到有关小明相关的信息。故事就是这么一回事,关注点就在这朋友圈上。朋友圈就是运用观察者模式的一个很好的样例。为什么这么说?我们发朋友圈的时候,那些没有屏蔽我们朋友圈的好友,会收到信息推送。也就是没有屏蔽我们朋友圈的好友其实是订阅了我们朋友圈,好友相当于观察者,我们是被观察的对象。符合观察者模式这个关系。

我们通过代码来描述小明、小红、小东他们在朋友圈玩的场景。利用观察者模式,需要观察对象和被观察对象,所以我们先定义 2 个接口,分别是 Observable (可被观察接口) 和 Observer (观察者接口)。

实现 Observable 接口的对象说明是可被订阅观察的,所以它需要 addObserver() 新增订阅者方法和 removeObserver() 移除订阅者方法,另外还有一个是必须的,就是通知各个订阅者消息的方法 notifyObservers()。那 Observable 接口代码如下所示。

1
2
3
4
5
interface Observable {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}

实现 Observer 接口的对象说明是可以去订阅观察的,也就是说可以接收被订阅的对象发出来的消息,那就需要一个接收消息的方法 update()。代码如下所示。

1
2
3
interface Observer {
void update(String name, String message);
}

为了让大家不混淆,先把观察者和被观察者分离开,其实在这个例子中,观察者和被观察者是同一个对象 User 的。这里就分开,分成 User 和 Friend,后面会给出正确的代码,稍安勿躁哈。这里 User 作为被观察者,实现了 Observable 接口,而 Friend 作为观察者,实现了 Observer 接口。代码如下。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class User implements Observable {

private List<Observer> friends;
private String name;

public User(String name) {
this.name = name;
this.friends = new LinkedList<>();
}

public void sendMessage(String message) {
this.notifyObservers(message);
}

@Override
public void addObserver(Observer observer) {
this.friends.add(observer);
}

@Override
public void removeObserver(Observer observer) {
this.friends.remove(observer);
}

@Override
public void notifyObservers(String message) {
this.friends.forEach(friend -> {
friend.update(this.name, message);
});
}
}

class Friend implements Observer {

private String name;

public Friend(String name) {
this.name = name;
}
@Override
public void update(String name, String message) {
System.out.println("【" + this.name + "】看到【" + name + "】发的朋友圈:" + message);
}
}

public class ObserverTest {

public static void main(String[] args) {
User xiaoMing = new User("小明");
Friend xiaoHong = new Friend("小红");
Friend xiaoDong = new Friend("小东");
xiaoMing.addObserver(xiaoHong);
xiaoMing.addObserver(xiaoDong);
xiaoMing.sendMessage("今天真开心");
// 小红和小明闹别扭了,小红取消订阅小明的朋友圈
xiaoMing.removeObserver(xiaoHong);
xiaoMing.sendMessage("希望明天也像今天一样开心");
}

}

打印结果:
【小红】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心

看到代码执行结果,小红和小东都订阅了小明的朋友圈,小明发了朋友圈:今天真开心。他们俩都收到了,因为小红和小明闹别扭,小红取消订阅小明的朋友圈,所以小明后来发的朋友圈,小红没收到。

上面代码其实是不对的,不应该用 User 和 Friend 2 个类来定义。如果小明订阅小红和小东的朋友圈呢?这样实现比较麻烦,主要是为了分清 观察者 和 被观察者 这 2 个概念,通过上面的例子应该分清楚了 2 个概念了,那就可以来看正确的代码,小明、小红、小东他们其实都是观察者和被观察者,所以我们用 User2 来定义他们就可以,User2 实现了 Observable 和 Observer 接口。代码如下。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class User2 implements Observable, Observer {

private List<Observer> friends;
private String name;

public User2(String name) {
this.name = name;
this.friends = new LinkedList<>();
}

@Override
public void addObserver(Observer observer) {
this.friends.add(observer);
}

@Override
public void removeObserver(Observer observer) {
this.friends.remove(observer);
}

@Override
public void notifyObservers(String message) {
this.friends.forEach(friend -> {
friend.update(this.name, message);
});
}

@Override
public void update(String name, String message) {
System.out.println("【" + this.name + "】看到【" + name + "】发的朋友圈:" + message);
}

public void sendMessage(String message) {
this.notifyObservers(message);
}
}

public class ObserverTest {

public static void main(String[] args) {
User2 xiaoMing2 = new User2("小明");
User2 xiaoHong2 = new User2("小红");
User2 xiaoDong2 = new User2("小东");
xiaoMing2.addObserver(xiaoHong2);
xiaoMing2.addObserver(xiaoDong2);
xiaoMing2.sendMessage("今天真开心");
xiaoMing2.removeObserver(xiaoHong2);
xiaoMing2.sendMessage("希望明天也像今天一样开心");

xiaoHong2.addObserver(xiaoMing2);
xiaoHong2.addObserver(xiaoDong2);
xiaoHong2.sendMessage("今天和小明吵架了,屏蔽他的朋友圈");

xiaoDong2.addObserver(xiaoMing2);
xiaoDong2.addObserver(xiaoHong2);
xiaoDong2.sendMessage("小明和小红吵架了,夹在中间好尴尬");
}

}

打印结果:
【小红】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心
【小明】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈
【小东】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈
【小明】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬
【小红】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬

从代码中,我们看到小明、小红、小东 3 个人互相订阅朋友圈,当然中途小红屏蔽了小明的朋友圈。这就是 观察者 和 被观察者 刚好是同一个对象的实现。

总结

观察者模式 是一个比较特殊的设计模式,它定义了触发机制,观察者只要订阅了被观察者,就可以第一时间得到被观察者传递的信息。在工作中,使用观察者模式的场景也比较多,比如消息队列消费,Android 开发中的事件触发机制等等。好,观察者模式就到这。

推荐阅读:

行为型模式:迭代器模式

行为型模式:策略模式

行为型模式:责任链模式

设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。

LieBrother

读书 | 颠覆者:周鸿祎自传

发表于 2019-03-02   |     |   阅读次数

周鸿祎自传

文末送书福利:《颠覆者:周鸿祎自传》

作为老周旗下的产品线,公司里随处可见的就数这本书了。春节长假时间抽空读了这本书,分享一下这本书的大概内容以及我的一些感受。

从小与众不同

书里的老周很小的时候就展现了与众不同的地方,他用非主流称自己。喜欢在教室捣乱,捣乱的前提是老师教的他都会了,在校园打架伸张正义,套着学渣的皮却是学霸的料。这种人应该是最招同学“讨厌”的,真学渣跟他玩疯确看他成绩前茅,真学霸看他一副学渣样自己成绩确比不上他。虽然喜欢捣乱,但是阅读确是他喜欢并一直坚持的事。老周当初刚接触计算机,为了看到更多计算机方面的书籍,不惜代价把自己喜欢的邮票给了同学;因为同学家有杂志,所以经常去同学家串门看杂志等等。回想我们自己年少,是不是少了一点求知欲?

计算机梦

在这问个问题:有多少人在高考前知道自己想读什么专业或者自己对某个领域很有兴趣么?这个问题一直触发我去思考教育的本质。我们大多数人寒窗苦读 12 年,却在填志愿的时候懵逼了,包括我在内。我现在还非常感激当初把我带入计算机坑的人。而老周在很早就决定入坑计算机,即使在没有其他录取书的前提下收到华南理工的食品工程专业录取书也依旧坚持要学计算机。而最终真的如他所愿入学了西安交通大学计算机专业。有了兴趣,有了方向,只管干就是了。

多姿多彩的大学

进入大学,老周依旧跟学渣一般捣乱。脑袋灵活的老周也开始了自己的商业王国,用高中高考参考书去学校摆摊卖旧书,编写算命小程序去给人算命。技术上,老周沉迷于上机,经常去蹲点看有没有人上机课没去上,有的话他就跑进去上机,有一次还攻破了西安交大的计算机中心。大三开始就去外面接项目做,为了增加收入和想去验证自己在真实世界面的能力。应该有不少同学在大学的时候兼职过,那时的你是不是只是为了增加收入?还是有想过去看看自己的实力?到了研究生,老周开始了第一个真正意义上的创业:研发反病毒卡,当然还是经常晚上蹭学校的电脑,反映出当时计算机对一个学生来说是个奢侈的东西,后面还因为这时被警察抓了,怀疑学校的一些电脑零件是被老周一起研发的几位小伙伴偷的,在公安局被审问折磨了 2 天 2 夜。虽然最后这个反病毒卡创业失败,但老周这个过程是非常主动积极,尝试去做很多事,比如销售,找客户等等,他认为这些尝试都是必要的,不拘泥于技术。现在很多人工作都会把自己的工作和别人的工作分得很清楚,只做自己份内的事,这没有什么不好,我觉得这种做法会把自己圈在一个围栏里,这个围栏要变大很难,甚至在慢慢变小,把心放宽,接受更多不同的信息,去做不同的事,像乔布斯说的:你们现在同样不可能从现在这个点上连出通向未来的那一条线。只有当你回头看时,才会发现这些过去的点其实已经画出了那条线。所以,要相信每一个点迟早都会连接到一起。这个的前提是你要主动去对接触更多的点,才能在以后连成线。在工作中主动去承担更多的责任,责任越大,能力越大。

从打工做起

老周经历了 2 次创业失败后决定去打工,入职了北大方正。入职之初依旧因为易闯祸的性格,被别人误认为是个不好好做事、想一步登天的狂妄分子。后面老周决定去公司最艰苦的地方工作:新疆。这一次老周是认真的。而做的第一件事就让同事们转变了对他的态度,那就是帮建设银行的一套软件做一个菜单系统,而这个界面实现技术刚好是他研究生毕业设计时用过的。我们可以借鉴老周的做法,当然不是刻意去展现自己,而是真的发现公司哪些地方不足,然后用自己学过的知识去解决它。这个说起来简单,做起来难,很多人只做到了第一步:发现问题。而最重要的是第二步:解决问题。企业要的是一个会解决问题的人,而不是会发现问题的人。在北大方正的仕途可以说是一帆风顺,但是最终因为意识到方正还沉迷于照排、出版印刷系统的开发,做着很多曲高和寡的东西,有些和市场脱节,老周选择辞掉这份稳定的工作。不过这时候的老周,已经从单纯的程序员转变为优秀的管理者。

创业心永不变

创业依旧是老周想干的事。开始做的产品是 3721,实现在地址栏中直接输入中文就可以用,不用输入英文。这个产品让老周经历了很多事情,比如第一次去融资、和职业经理人冲突、跟同行业的企业竞争、跟政府机构对抗、和百度对簿公堂,到最终卖给了雅虎。我认为走过这个过程的老周,已经是一个很了不起的创业家。卖给了雅虎后,老周担任了雅虎中国区的总经理,但是没有公司控制权,所以让老周处于比较危险的位置。进入雅虎之后,老周干得并不顺心,因为中国本土文化和美国公司文化的冲突很大,老周是属于那种直来直去的办事风格,但是雅虎却是那种规规矩矩做事的风格。在老周的带领下,雅虎开始发力搜索,做邮件系统,一度做得不错。可是老周的野心却频频招到雅虎总部的冷落,最终受不了的老周,辞职雅虎。只有自己创办企业才能满足老周的野心,他是一个不愿意受束缚的人。

奇虎

因为中国有“骑虎难下”这个成语,老周希望网民上了这个网站就再也难以下去。这就是奇虎的来源。奇虎刚开始是做社区搜索,也就是在百度已经占据了网页搜索的制高点时,老周选择社区搜索避免直接和百度竞争。刚开始做的并没有收到预期的效果,恰巧到了 2006 年,流氓软件开始在网络上肆意横行,严重干扰了网民的上网体验,而网民中大多数把“流氓软件”的罪名强加在老周头上。为什么会赖在老周头上?因为 3721 的插件模式是老周发明的,这种插件模式很不尊重用户,老周也公开承认过,网民们并不买账。从这时起,老周走上了杀毒的这条道路,起初是为了把流氓软件干掉。360安全卫士上市后,用户增长得很快,竞争对手也激烈反弹,有很不错的效果。有个小冲突,老周把以前自己开发的 3721 插件 (雅虎上网助手) 给纳入了流氓软件范围,引来了和雅虎的口水战,虽然是负面的消息,但是却给360安全卫士一次曝光的机会,在这个过程中360安全装机量高涨。最重要的一件事是,360安全卫士走向免费这个路线,它颠覆了传统的杀毒软件行业,最终成为用户量最多的杀毒软件。从走上杀毒取得了一些成功,到后面360慢慢扩展商业布局,比如360安全浏览器、360安全网址等等,这一切看是偶然,其实是抓住了行业的趋势,老周很早就看到了免费趋势。

3Q 大战

2010 年春节的时候,腾讯QQ医生开始在 PC 端进行强制捆绑,因为腾讯拥有 QQ 这个庞大的用户群,只要一推广就很容易覆盖到所有的网民。QQ医生第一次推广没有达到预期效果,因为产品还不够成熟,但是已经给老周敲了个很大的警钟。而腾讯第二次进攻已经改名为“QQ电脑管家”,并具备了云查杀木马、系统漏洞补休、安全防护等等,这次直接对抗 360。360 通过分析 QQ 软件,发现它对用户的电脑有扫描硬盘的行为,但是没有告知用户,所以老周召集程序员开发了 360隐私保护器,功能是帮助用户监控电脑中的软件在系统后台的所有行为,发布之后,会将名字为 QQ.exe 的文件都表示为窥视隐私的软件。腾讯也发起联合声明进行声讨,这场大战就正式开始了。后面 360 又开发了一款“360扣扣保镖”软件,可以过滤 QQ 广告、清理 QQ 垃圾,发布之后核弹引爆了,接着被举报到公安局,老周差点被抓,没被抓是因为上班前收到电话直接跑到了香港去了。腾讯作为回应,发布了著名的“艰难决定”–腾讯QQ与360安全卫士互不兼容。最后只能靠政府介入,让腾讯和360放弃争执。现在 2019 年,经历过 3Q 大战的腾讯变得更加强壮,而 360 依靠着安全正快速往生态发展。现在的互联网也是阿里、腾讯 2 家独大,包揽了中国互联网的大多数用户,任何一家创业公司都害怕与他们竞争,如果有直接竞争,要么是卖身,要么像 360 一样拼死对抗,只是现在越来越少能像 360 那样与它们抗衡的企业了。

敲钟

360 上市其实和 3Q 大战是并行走的,经历过 3Q 大战,经历了承销商替换的过程,以及时间紧急,但是最终还是成功在纽交所上市。

总结

平时我们只看到了牛人光彩的一面,却不知他是经历了什么才能到达今天的成就。看了这个自传,也算了更加深入的了解老周以及 360。以后我也要多花点时间看更多的人物自传,去了解每个人不同的一面。

重点来啦

福利福利,公众号第一次送书活动开启。
今天第一次给大家分享读书的感受,以后也会将我读过的觉得不错的书推荐给大家,希望大家一起来读好书。说不如做,自费送大家 1 本《颠覆者:周鸿祎自传》,包邮。号主也是个打工仔,没啥钱,等有机会升职加薪了送大家更多的福利。送书活动会在以后分享的读书感受中持续。

抽奖规则:

  1. 抽奖活动:需要关注公众号 LieBrother,在公众号后台回复【周鸿祎自传】,即可收到抽奖图片。(没有关注公众号的同学中奖无效)
  2. 活动截止时间:3 月 9 号 22:00 。
  3. 中奖的同学记得微信加我好友领取奖品,一周内有效。微信号:CSHJustDo

公众号

1…567…24
LieBrother

LieBrother

当才华撑不起野心时,应该静下心来学习;当能力驾驭不了目标时,应该沉下心来历练。

120 日志
38 分类
138 标签
© 2016 - 2019 LieBrother
由 Hexo 强力驱动
主题 - NexT.Mist
本站访客数人次  |  本站总访问量次