LieBrother

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


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

接口隔离原则

发表于 2019-01-04   |     |   阅读次数

景

设计模式六大原则之四:接口隔离原则。

简介

姓名 :接口隔离原则

英文名 :Interface Segregation Principle

价值观 :宁缺毋滥

个人介绍 :

  1. Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该依赖它不需要的接口。)
  2. The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上。)

也用一个故事来讲这 2 句干巴巴的定义。

一小伙子跑到大城市的工厂打工,工作了一年半载,越来越觉得没劲,每天干那么多活,又领那么一点工资,和他老爸抱怨这段时间的困扰,老爸想着,家里有个小作坊,自己也一年不如一年了,要不就让儿子回老家管理这小作坊。小伙子熬不过这个年,就跑回老家跟着老爸打理小作坊。

布娃娃
(来自Google Image)

小作坊主要是做布娃娃的,如上图,工作在于打扮包装布娃娃,工序有给布娃娃扎辫子、穿衣服、包装入箱、打标签。整个完整的流程都是一个人做的。有很多个工人每天都在做这个事情。

老爸向小伙子诉苦,感觉招工挺多人的,生产力还是提不上去。小伙子记着老爸的话,在工厂里面观察了几天,他发现每个工人都要做这 4 个打扮包装布娃娃的工序,有些工人扎辫子很快但穿衣服很慢,有些工人扎辫子很慢但穿衣服快,他用了笔记本记下来:李大姨扎辫子快,王大妈穿衣服快,就这样把每个人有效率的工作都记录下来。

一天晚上吃饭,小伙子跟老爸说了自己观察到的现象,也把本子拿给老爸看,跟老爸商量:可不可以做个尝试,不要每个人负责打扮包装布娃娃全步骤,而是按工序分开,每个人只负责一个工序,每个工人只干一件事,更容易熟能生巧。老爸听着觉得有道理。

第二天早上,就到小作坊里,召集了所有工人,按小伙子的笔记上面的名单分工,大家都做好各自负责的内容,像流水线一样,做好了就放到下个工序的地方,让下个工序的人去做。到了下班,小伙子清点了今天工作的成果,包装完成的娃娃比前一天多了 50% 。晚上小伙子跟老爸喝着百威吃起大肉庆祝一番。

这个故事你看了可能想骂爹骂娘,跟上面的定义有啥毛关系?故事只是把大家带入这个场景,我们在工作中,着手开发之前不都得先理清好需求背景,这就是要讲接口隔离原则的背景,通过代码来给大家讲解一下如何用好接口隔离原则。

父亲的运营模式

先看代码

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
interface Work {

void hairBraiding();
void getDressed();
void packingIntoTheBox();
void makeTag();

}

class WangMather implements Work{

@Override
public void hairBraiding() {
System.out.println("王大妈给布娃娃扎辫子");
}

@Override
public void getDressed() {
System.out.println("王大妈给布娃娃穿衣服");
}

@Override
public void packingIntoTheBox() {
System.out.println("王大妈把布娃娃装入箱子");
}

@Override
public void makeTag() {
System.out.println("王大妈给箱子打标签");
}
}

class LiAunt implements Work {

@Override
public void hairBraiding() {
System.out.println("李大姨给布娃娃扎辫子");
}

@Override
public void getDressed() {
System.out.println("李大姨给布娃娃穿衣服");
}

@Override
public void packingIntoTheBox() {
System.out.println("李大姨把布娃娃装入箱子");
}

@Override
public void makeTag() {
System.out.println("李大姨给箱子打标签");
}
}

// 测试代码
WangMather wangMather = new WangMather();
wangMather.hairBraiding();
wangMather.getDressed();
wangMather.packingIntoTheBox();
wangMather.makeTag();

LiAunt liAunt = new LiAunt();
liAunt.hairBraiding();
liAunt.getDressed();
liAunt.packingIntoTheBox();
liAunt.makeTag();

在父亲管理下的小作坊,是大家各自完成好一个布娃娃,工作互不交接,在这种运营模式下,我们把所有工作都合并在一个接口 Work 是没有问题的。有人可能要问,不是说接口隔离么?这里面 Work 接口的 4 个方法都可以分离开,它们都是各自的工作内容。稍等一下,我们现在是基于老父亲运营的模式下实现,如果小作坊一直都是这种模式运营,这段代码有问题么?其实没问题的,我们根据当时的业务考虑,在这种情况下,把 Work 抽成 4 个接口不是不可以,只是不现实,每个工人都去实现一模一样的 4 个接口在老父亲运营模式下是不切实际。

儿子的运营模式

接下来介绍儿子的运营模式。儿子提倡的是每个工人职责分明,只负责一个事情,在这种情况下,如果还是用老父亲的 Work 接口会有什么问题呢?上面我们说了,李大姨扎辫子快,王大妈穿衣服快,所以李大姨被分配去给布娃娃扎辫子,王大妈被分配去给布娃娃穿衣服。我们沿用老父亲的 Work 接口实现,代码如下

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
class WangMather2 implements Work{

@Override
public void hairBraiding() {
}

@Override
public void getDressed() {
System.out.println("王大妈给布娃娃穿衣服");
}

@Override
public void packingIntoTheBox() {
}

@Override
public void makeTag() {
}
}

class LiAunt2 implements Work {

@Override
public void hairBraiding() {
System.out.println("李大姨给布娃娃扎辫子");
}

@Override
public void getDressed() {
}

@Override
public void packingIntoTheBox() {
}

@Override
public void makeTag() {
}
}

看出问题来了么?李大姨仅仅参与扎辫子工作,王大妈参与了穿衣服工作,但是却都要依旧实现其他 3 个多余的接口。所以在儿子的运营模式下,老父亲的 Work 接口需要重新分配,以工序的角度分配,而不是以完成一个布娃娃的角度分配。总共有 4 个工序:扎辫子、穿衣服、包装入箱、打标签,我们需要定义 4 个接口,让员工去实现各自负责的工序接口。代码如下

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

interface Hair {
void hairBraiding();
}

interface Dress {
void getDressed();
}

interface Box {
void packingIntoTheBox();
}

interface Tag {
void makeTag();
}

/**
* 李大姨给布娃娃扎辫子快
*/
class LiAunt3 implements Hair {

@Override
public void hairBraiding() {
System.out.println("李大姨给布娃娃扎辫子");
}
}

/**
* 王大妈给布娃娃穿衣服快
*/
class WangMather3 implements Dress{

@Override
public void getDressed() {
System.out.println("王大妈给布娃娃穿衣服");
}

}

/**
* 陈大叔包装快
*/
class ChenUncle implements Box {

@Override
public void packingIntoTheBox() {
System.out.println("陈大叔给布娃娃装箱");
}
}

/**
* 黄大姐贴标签快
*/
class HuangSister implements Tag {

@Override
public void makeTag() {
System.out.println("黄大姐给箱子打标签");
}
}

// 测试代码
LiAunt3 liAunt3 = new LiAunt3();
WangMather3 wangMather3 = new WangMather3();
ChenUncle chenUncle = new ChenUncle();
HuangSister huangSister = new HuangSister();
liAunt3.hairBraiding();
wangMather3.getDressed();
chenUncle.packingIntoTheBox();
huangSister.makeTag();

这段代码看起来就很清晰了,在儿子的运营模式下,大家都是只做一道工序,这样子实现就非常合理。看了这个过程,你理解了接口隔离原则了么?再看一看上面的定义:客户端不应该依赖它不需要的接口。闭上眼睛,静默 3 秒,感受一下。
我们也可以回忆一下在工作中编写的代码,是不是有遵守接口隔离原则?在特定的场景下,如果很多类实现了同一个接口,并且都只实现了接口的极少部分方法,这时候很有可能就是接口隔离性不好,就要去分析能不能把方法拆分到不同的接口。

总结

接口隔离原则最最最重要一点就是要根据实际情况,具体业务具体分析,不能犯了上面说到的错误:在老父亲的运营模式下,按儿子的工序划分接口去实现,那样子会得不偿失。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章



LieBrother

公众号

依赖倒置原则

发表于 2019-01-01   |     |   阅读次数

雪

2019 新年第一篇。

设计模式六大原则之三:依赖倒置原则。

简介

姓名 :依赖倒置原则

英文名 :Dependence Inversion Principle

价值观 :大男子主义的典型代表,什么都得通过老大或者老爸同意。

伴侣 :一定是个温柔体贴的女子。

个人介绍 :

  1. High level modules should not depend upon low level modules.Both should depend upon abstractions. 高层模块不应该依赖低层模块,两者都应该依赖其抽象(模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的)
  2. Abstractions should not depend upon details. 抽象不应该依赖细节(接口或抽象类不依赖于实现类)
  3. Details should depend upon abstractions. 细节应该依赖抽象(实现类依赖接口或抽象类)

给大家讲个故事,我胡乱想的,如有雷同,肯定是英雄所见略同。那必须交个朋友。

一个小村里,有两家饭馆,虽然挂着不同的牌子,挨在一起,但是老板确是表兄弟。这两兄弟抠得很,为了节省成本,密谋了一个想法:在两家饭馆谁家忙的时候,可以让不忙的那家的员工过去支援一下。这样子,本来每家饭馆都需要 2 个洗碗工,总共需要 4 个,他们就只招了 3 个,省了 1 个洗碗工的成本,当然不止洗碗工,还有服务员等等。两兄弟约定了规则:

  1. A 饭馆需要支援的时候,B 饭馆老板,让 B 饭馆老板选哪个员工去支援,不能直接让 A 饭馆的员工直接找 B 饭馆的员工去帮忙,但可以让 A 饭馆员工找 B饭馆老板告知需要支援。
  2. 虽然老板权利大,但是也不能说 A 饭馆老板直接叫 B 饭馆的员工去帮忙。
  3. 员工没有真实的老板,今天为 A 饭馆工作就是 A 饭馆的员工,没有跟定哪个老板。

大概通过这个小故事,描述了依赖倒置原则的基本内容。

代码体现

下面通过代码来模拟这个故事。

错误的示范

这个错误的示范将就看哈,可能有些问题没描述清楚。

老板和员工抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class Boss {

abstract void support();

abstract void askHelp(Boss boss);
}

abstract class Staff {

private String name;

abstract void service();

abstract void askHelp(Boss boss);

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

老板具体类

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
class BossA extends Boss {

private StaffA staffA;

public BossA(StaffA staffA) {
this.staffA = staffA;
}

@Override
void support() {
staffA.service();
}

@Override
void askHelp(Boss boss) {
boss.support();
}

}

class BossB extends Boss {

private StaffB staffB;

public BossB(StaffB staffB) {
this.staffB = staffB;
}

@Override
void support() {
staffB.service();
}

@Override
void askHelp(Boss boss) {
boss.support();
}
}

员工具体类

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
class StaffA extends Staff {

public StaffA(String name) {
this.setName(name);
}

@Override
void service() {
System.out.println(this.getName() + "提供服务");
}

@Override
void askHelp(Boss boss) {
boss.support();
}
}

class StaffB extends Staff {

public StaffB(String name) {
this.setName(name);
}

@Override
void service() {
System.out.println(this.getName() + "提供服务");
}

@Override
void askHelp(Boss boss) {
boss.support();
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
/** 初始化老板和员工 */
StaffA staffA = new StaffA("A 员工");
StaffB staffB = new StaffB(" B 员工");
Boss bossA = new BossA(staffA);
Boss bossB = new BossB(staffB);

/** A 老板向 B 老板求支援 */
bossA.askHelp(bossB); // 打印出:B 员工提供服务

/** B 员工向 A 老板求支援 */
staffB.askHelp(bossA); // 打印出:A 员工提供服务

好像看起来实现了要求了,但是其实这段代码没有按照上面的 3 点规则编写,破坏了第 3 点规则,老板们的员工没有用员工的抽象类,破坏了细节依赖抽象这一点。设想一下,假如现在 A 老板把 A 员工辞退了,重新招了个 C 员工,那么怎么实现呢?是不是需要再新增一个 StaffC 类,然后再修改 BossA 类代码,把 StaffA 换成 StaffC。这样超级麻烦,在平时写项目中要时刻考虑这一点:在具体实现类使用其他类,是不是可以用其抽象类?

代码:

DIPErrorTest.java

正确的示范

看了上面那个憋屈的代码,再来看下面简洁的代码,才会发现依赖倒置原则是多么强大。

老板和员工抽象类

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
abstract class Boss2 {

private Staff2 staff;

public Boss2(Staff2 staff) {
this.staff = staff;
}

abstract void support();

abstract void askHelp(Boss2 boss);

public void setStaff(Staff2 staff) {
this.staff = staff;
}

public Staff2 getStaff() {
return staff;
}
}

abstract class Staff2 {

private String name;

abstract void service();

abstract void askHelp(Boss2 boss);

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

老板类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BossImpl extends Boss2 {

public BossImpl(Staff2 staff) {
super(staff);
}

@Override
void support() {
this.getStaff().service();
}

@Override
void askHelp(Boss2 boss) {
boss.support();
}
}

员工类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class StaffImpl extends Staff2{

public StaffImpl(String name) {
this.setName(name);
}

@Override
void service() {
System.out.println(this.getName() + "提供服务");
}

@Override
void askHelp(Boss2 boss) {
boss.support();
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** 正确示范 */
Staff2 staffA2 = new StaffImpl("A 员工");
Staff2 staffB2 = new StaffImpl("B 员工");
Boss2 bossA2 = new BossImpl(staffA2);
Boss2 bossB2 = new BossImpl(staffB2);

/** A 老板向 B 老板求支援 */
bossA2.askHelp(bossB2); // 打印出:B 员工提供服务

/** B 员工向 A 老板求支援 */
staffB2.askHelp(bossA2); // 打印出:A 员工提供服务

/** A 老板辞退了 A 员工,换成了 C 员工 */
Staff2 staffC2 = new StaffImpl("C 员工");
bossA2.setStaff(staffC2);

/** B 员工向 A 老板求支援 */
staffB2.askHelp(bossA2); // 打印出:C 员工提供服务

这代码相比上面错误的示范,简洁了很多,实现的功能却更灵活,这就是依赖倒置原则强大的地方,它可以将类的耦合性降低,提供灵活的处理。

代码:

DIPRightTest.java

最佳实践

  1. 变量的表面类型尽量是接口或者是抽象类
  2. 任何类都不应该从具体类派生
  3. 尽量不要覆写基类的方法
  4. 结合里氏替换原则使用
    (来自《设计模式之禅》)

总结

总的来说,要实现依赖倒置原则,要有『面向接口编程』这个思维,掌握好这个思维后,就可以很好的运用依赖倒置原则。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章



公众号

我的 2018

发表于 2018-12-31   |     |   阅读次数

景

在前进的路上,别忘了走过的路。

昨晚写了设计模式原则第三篇『依赖倒置原则』,写得比较晚,所以没有发布,想着今天早上来发。早上醒来,看了朋友圈,才发现今天是 2018 最后一天,内心有一股强大的力量促使我暂停发布昨晚的文章,今天做一波总结,写一篇总结文章,给 2018 一个交代,也给 2019 一个期待。生活好像就得有一些仪式感。

这是我的第一次年度总结,没有时间去准备、去构思,只是心血来潮,只能根据时间线,记录一下这一年发生的人与事,回忆当时那个场景,感受那时的心灵,反思那会的思想。

多灾多难的开端

元旦到过年这一个半月没有什么触发内心的,甚至想起写这篇文章的时候,心里就是以过年后的时间线开始,可能是正月发生的事情真的很触动我的心灵。过年走亲戚闲聊都在说今年的经济会走下滑,因为十年一轮回的经济已经从 2008 年后深得人心,心里想着这些是大家都需要经历的事情,没有很特别需要注意的,可接下来发生的事情,让我真的感觉这一年有点悬。

年后上班,没有啥特别的,还是一个人住一个单间的,在 2 公里远的写字楼上班。记得是上班的第二天,晚上睡觉前好好的,第二天起来发现左膝盖不对劲,动不了,弯曲都很刺痛。这种状况持续了 2 周,期间贴了很多膏药,算恢复得不错。那 2 周停止了任何的运动,包括爬楼梯、打球、骑单车。其实从小就膝盖不怎么好过,经常会无缘无故的酸痛,还记得小时候晚上痛得不行哭起来,长大后基本上就没有出现膝盖酸痛了。经过大概一个月的修养,也不算修养,只是特别注意膝盖的一些运动,恢复得差不多了,也可以正常打球,算是 2018 第一件记忆犹新的事情。

厄运来临。一个周日早上,在公司加班,大姐突然发了微信过来,其中发了一张车祸的照片,从照片看不清是谁,因为整个人的脸都肿了,没法看清五官。大姐说是二伯出车祸了,很严重。刚看完这一句,眼泪就掉下来了,那种感觉真的是平生第一次感受到,言语无法表达这种感觉。忍着心疼的心情,立马打电话给老爸,他那会正在警察局里面处理这件事,我问他严重么?他回了应该不行了,要有心理准备。这时我已经说不出话来,怕对面父亲听到,只能草草说爸你先去忙。那段时间整个家族心情都很沉重,很多人都睡不着觉,最后二伯还是走了。每当夜里想起这件事还会流眼泪,因为二伯是最疼我的,从小到大,小时候最喜欢去他家,他会带我去他的菜园玩、去抓青蛙,等到我们那边有了游泳池,虽然也要十几公里,但是他还是会骑着电车载我去游泳,游泳回来后就去吃牛肉粿条,整个夏天几乎每周都去。想到这里,我反思了自己,自从毕业工作后,很少很少去拜访叔伯,这让我很触动,不知道大家在外面工作有没有也很少去关心家里的长辈?有的话,希望我们都有所改变,我也准备今年过年每家每户都去拜访,不再让自己遗憾。

这些事之后,在和家人闲聊中,感慨今年是个多事年。

跳槽

这次跳槽,算是一次 follow my heart,毕业之后来到招商银行这边做开发,来之前就听一些师兄指导,要做技术的话,可能不是一个好的选择。年轻气盛的我并不赞同,觉得这取决于个人,而不是环境。还记得当初离开实习的单位,和 HR 交流,说了一句:自己选择的路,跪着也要走完。这一年半不算是跪着走完的,我这么认为,每个人走过的路都是有价值的。在招行领导确实也很器重,让我加入了新的项目组,负责更多新的东西,包括后台框架 Spring Cloud、前端框架 Angular、云平台运维等等。让我触动要离职的主要原因是要进互联网企业。这一年半虽然不长,但是和一些在互联网工作的朋友交流过程中,我发现和他们的差距已经不小了,不管从技能还是其他方面都是。复习了一段时间后,拿到了 offer,并提出了离职。领导找了谈话,从早上 9 点谈到了下午 5 点,期间没喝过一滴水、没上过洗手间、更别说吃饭了。总的来说,心里一直很感激领导以及同事们这一年半的照顾。

来到了 360金融 这边做风控平台相关的工作,对我来说是一个新的领域,之前在银行接触的是一些账户相关的业务,来这边接触的是风控相关的业务。目前还在熟悉还在加速学习。以后有机会可以给大家分享一些这方面的知识。

花光所有钱

第一次感受到花光所有钱的感觉。工作了一年半,攒了十来万,年中的时候全花完。老家宅基地要打桩,总共需要 20 多万,和我哥一起分摊,大概十来万,全都花光光,没有啥特别的感觉,但是就是觉得真的可以一件事就把你的钱全掏空,不管你有多少钱,总有哪些一些事情去匹配到你拥有的那个价位。因为有了这么一件事,所以在后面和朋友或者前辈交流的过程中,偶尔会谈到这个问题,有朋友同时毕业,她所有钱也都花光在家里购置宅基地的这些事情上,前辈交流中,他们也表示很能理解刚毕业前 5 年出现的这种事情,他们说是正常的,特别是咱们这些从农村来到大城市打拼的人,父母辛辛苦苦把咱们养大成人,我们赚到钱也需要在老家的一些基础设施上,也算是让父母能够更好的生活。虽然只是打桩,并没有改变很多,但是会慢慢去改善家里的生活。今年还发现老家因为建设规划多了一条路,现在的房子会被拆掉,这次打桩,也算是为以后的居住做一点点准备。

租客的无奈

5 月份换了工作,搬了一次家,农民房,什么家居都得自己安排上,也算是安稳下来;才住了 2 个月,就被房东通知楼房需要改造,限时一个月搬出,挺蛋疼的。这时才感受到租客的无奈,有人戏称,在大城市就是给房东打工,一间不到 30 平方的房,一个月 2200 的房租,还仅仅是农民房,随时都可能被赶走。虽然这些都是不争的事实,以个人之力也无法改变,所以只能积极一些想,让自己强大起来。用 NBA 经常看到的那句话『那些不能击垮你的,终将使你强大』。

领证

2018.12.12 这个日子应该终生铭记。和女朋友结束了 7 年的异地恋,在这个日子里,我们领了人生一大证:结婚证。程序猿不是应该是单身狗么?哈哈,在做程序员之前谈的,可能当时有远见,要是当时没谈,当上程序员之后,估计现在还是单身。没想到这么快,就已经到了该结婚的年龄,今年身边的朋友、同学等等,很多都结婚了。就像一位好友说的:成家了,该立业了。 嗯,有那么一句话:『能力越大,责任越大』,现在责任大了,能力也得跟上。加油吧少年。

分享

12 月开始重新写公众号、博客,也算是一个想让自己能力提高的方法,反思了过去陆陆续续记录下来的博客,主要原因还是缺乏坚持,经常写一段时间就断一段时间。记得目前印象中坚持最久的一件事是跑步,最长的是连续 55 天跑步。这一次不仅要坚持写,而且要把它当做产品来做,学习如何运营等等。为什么有这个想法呢?原因是今年看了一些产品的书籍,但是没有运用的地方,所以想尝试一下,这才是学习的一个好的方法。最重要的一点就是要分享对大家有用、有意义的东西,这样才能有所成长,不会浪费大家的阅读时间。期待你我共同进步。

那些琐碎的记录

看的书籍:

《你的团队需要一个会讲故事的人》
《软技能 代码之外的生存指南》
《从点子到产品:产品经理的价值观与方法论》
《人人都是产品经理——写给产品新人》
《风控:大数据时代下的信贷风险管理和实践》
《产品的视角:从热闹到门道》
《互联网金融风险控制》
《产品经理必懂的技术那点事儿:成为全栈产品经理》
《遇见未知的自己》
《阿里巴巴Java开发手册》
《MySQL技术内幕-InnoDB存储引擎》

还有一些忘记了,没有养成记录的习惯,也没有总结这些书的一些感悟,所以读过了就过了,接下来也要养成阅读的习惯,给自己一个要求,为读过的每一本书写一篇后感,和大家分享。

买了十几个极客时间的专栏,真正学习下去的时间屈指可数,得继续补充。

从 7 月份开始健身,主要目的就是锻炼,让身体更加健康,坚持下来了,基本每周最少去 2 次,继续保持。

总结

第一次写总结,回顾了 2018 这一年,没有很细腻去描述、没有浮夸的语言,只有那真实的内心写照。『别走得太快,有时候停下来,回想一下经历过的东西,会发现不一样的价值』这是年初阿里的面试官送的一句话,分享给大家,一起共勉。


LieBrother

wechatjpg

里氏替换原则

发表于 2018-12-28   |     |   阅读次数

景

设计模式六大原则之二:里氏替换原则。

简介

姓名 :里氏替换原则

英文名 :Liskov Substitution Principle

座右铭 :

  1. If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
    如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

  2. Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
    所有引用基类的地方必须能透明地使用其子类的对象。

这 2 个定义来自《设计模式之禅》,比较干巴巴,不认真思考起来可能不太容易懂。简单来说就是定义了什么是父子。在现实生活中,什么是父子?就是生你的那个男人和你的关系就是父子(父女)。而这里定义的就是假如 A 能胜任 B 干的所有事情,那 B 就是 A 的父亲,也就是儿子要会父亲的所有能活,儿子活得再烂也要有父亲的水平。

价值观 :很显然,比较传统,严父出孝子。儿子必须要有父亲的能耐,最好青出于蓝胜于蓝。

伴侣 :估计有个贤惠的老婆,才能有这么优秀的儿子。

个人介绍 :我比较严厉,也是为了生存没办法,只有一辈一辈地变优秀,一直坚持下去,家族就会越来越好。这样就可以富过三代,你看你们人类不是经常说富不过三代。。。扎心了老铁,老子还是富零代。

老爹开车,前方注意

里氏替换原则定义了什么是父子,还有一点要注意的,就是儿子不能在父亲会的技能上搞“创新”。
比如父亲会做红烧排骨,儿子在新东方烹饪学校中学到了一招,在红烧排骨里面加糖和醋,变成红烧糖醋排骨,更加美味,看代码,儿子在父亲的基础红烧排骨上加了糖醋,好像没啥问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Father1 {

public void braisedRibs(){
System.out.println("红烧排骨");
}

}


class Son1 extends Father1 {

public void braisedRibs(){
System.out.println("红烧糖醋排骨");
}

}

运行下面代码,会打印:红烧排骨

1
2
3
4
5
6
7
8
9
Father1 father1 = new Father1();
father1.braisedRibs();
```

我们上面说过,所有在使用父亲的地方,都能够替换成儿子,并且效果是一样的,那接下来我们改一下代码。

``` java
Son1 son1 = new Son1();
son1.braisedRibs();

结果是啥?打印出:红烧糖醋排骨,出乎意料吧。。。这结果完全不一样。想一下上面说的:老爸会的老子也要会,很明显,上面的例子老子不会红烧排骨,只会红烧糖醋排骨,所以这根本不是父子关系。

那应该怎么实现呢?其实红烧排骨和红烧糖醋排骨这压根就是 2 道菜,你去餐馆吃饭的时候,你点红烧排骨服务员给你送来红烧糖醋排骨,或者你点红烧糖醋排骨服务员给你送来红烧排骨,你这时候不生气,算我输。

来看看 Son2,Son2 将红烧糖醋改为 braisedSweetAndSourPorkRibs (翻译不好找 Google 算账去哈,反正不是我翻译的)。

1
2
3
4
5
6
7
class Son2 extends Father1 {

public void braisedSweetAndSourPorkRibs(){
System.out.println("红烧糖醋排骨");
}

}

测试一下是不是好儿子

1
2
3
Son2 son2 = new Son2();
son2.braisedRibs();
son2.braisedSweetAndSourPorkRibs();

打印出:
红烧排骨
红烧糖醋排骨

这才是 Father1 的好儿子嘛,不仅会红烧排骨,还会红烧糖醋排骨。所以说里氏替换原则就是在定义父子关系,大家都遵守这个定义,就会一代比一代好,不遵守大家也看到了,把前辈传下来的都毁于一旦了。

代码见:LSPTest.java

优缺点

下面再贴一下书本上的一些优缺点

优点

  1. 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
  2. 提高代码的重用性;
  3. 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
  4. 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
  5. 提高产品或项目的开放性。

缺点

  1. 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
  2. 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
  3. 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果————大段的代码需要重构。
    (来自《设计模式之禅》)

总结

好了,里氏替换原则的大概原理讲得差不多,大家只要记住是在定义“父子关系”,就像游戏规则一样,定义后让大家遵守,会让大家的程序在后面越来越复杂的时候也能清晰,而不会越来越乱。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章


LieBrother

wechatjpg

单一职责原则

发表于 2018-12-26   |     |   阅读次数

设计模式六大原则之一:单一职责原则

简介

姓名 :单一职责原则
英文名 :Single Responsibility Principle
座右铭 :There should never be more than one reason for a class to change. 应当有且仅有一个原因引起类的变更。。。意思就是不管干啥,我都只干一件事,你叫我去买菜,我就只买菜,叫我顺便去倒垃圾就不干了,就这么拽
脾气 :一个字“拽”,两个字“特拽“
伴侣 :老子职责单一,哪来的伴侣?
个人介绍 :在这个人兼多责的社会里,我显得那么的特立独行,殊不知,现在社会上发生的很多事情都是因为没有处理好职责导致的,比如,经常有些父母带着小孩,一边玩手机,导致小孩弄丢、发生事故等等

单一职责应用范围

单一职责原则适用的范围有接口、方法、类。按大家的说法,接口和方法必须保证单一职责,类就不必保证,只要符合业务就行。

方法

设想一下这个场景:假设我们要做一个用户修改名字以及修改密码的功能,可以有多种实现方案,比如下面列举 2 种实现方式
代码:SrpOfMethod.java

第一种实现方式

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
/**
* 错误的示范
*/
enum OprType {
/**
* 更新密码
*/
UPDATE_PASSWORD,
/**
* 更新名字
*/
UPDATE_NAME;
}

interface UserOpr {
boolean updateUserInfo(User user, OprType oprType);
}

class UserOprImpl implements UserOpr {

@Override
public boolean updateUserInfo(User user, OprType oprType) {
if (oprType == OprType.UPDATE_NAME) {
// update name
} else if (oprType == OprType.UPDATE_PASSWORD) {
// update password
}
return true;
}
}

第二种实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 正确的示范
*/
interface UserOpr2 {
boolean updatePassword(User user, String password);
boolean updateUserInfo(User user);
}

class UserOprImpl2 implements UserOpr2 {

@Override
public boolean updatePassword(User user, String password) {
user.setPassword(password);
// update password
return true;
}

@Override
public boolean updateUserInfo(User user) {
// update user info
return true;
}
}

2 种实现有什么区别呢? 第一种实现通过 OprType 类型的不同来做不同的事情,把修改密码和修改名字耦合在一起,容易引起问题,只要稍不注意,传错枚举值就悲剧了,在代码中也没法很直接看到是做什么操作,也就是这个方法的职责不明确。而第二种实现,把修改密码和修改名字分离开来,也就是把修改密码和修改名字都当做独自的职责处理,这样子就很清晰明了,你调用哪个方法,就很明确的知道这个方法是实现什么逻辑。结论是啥呢?用第二种方式实习才符合单一职责原则。现实中看到很多像第一种实现的代码,而且是枚举有十来个的情况,看代码真费劲。

接口

设想一下这个场景,假设我们让小明去倒垃圾,小红去买菜,小红回来后再叫小红去洗碗。下面也举 2 个实现的例子。
代码:SrpOfInterface.java

第一种实现方式

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
/**
* 错误的示范
*/
interface Housework {
void shopping();
void pourGarbage();
}

class XiaoMing implements Housework {

@Override
public void shopping() {
// 不购物
}

@Override
public void pourGarbage() {
System.out.println("pourGarbage ...");
}
}

class XiaoHong implements Housework {

@Override
public void shopping() {
System.out.println("shopping ...");
}

@Override
public void pourGarbage() {
// 从不倒垃圾
}
}

中途回来小红去洗碗,要怎么实现?按这个写法,就在 Housework 接口添加 washingUp() 方法,然后小明和小红依次都实现洗碗这个方法,只是小明不做具体实现代码,这样子是不是觉得很别扭,不符合单一职责原则的,修改一个地方,不影响其他不需要改变的地方,只对需要用到的地方做修改。小明本来就不用洗碗,却要去实现洗碗这个方法。

第二种实现方式

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
/**
* 正确的示范
*/
interface Shopping {
void doShopping();
}

interface PourGarbage {
void doPourGarbage();
}

interface WashingUp {
void doWashingUp();
}

class XiaoMing2 implements PourGarbage {

@Override
public void doPourGarbage() {
System.out.println("pourGarbage ...");
}
}

class XiaoHong2 implements Shopping, WashingUp {

@Override
public void doShopping() {
System.out.println("shopping ...");
}

@Override
public void doWashingUp() {
System.out.println("washing up ...");
}
}

可以看到,这种实现把不同的家务都当做不同的职责,分离开来,这种实现可以按需实现做家务的类型,小明只需要去倒垃圾,就实现 PourGarbage 接口,小红去购物和洗碗,就实现 Shopping 和 WashingUp 接口,完全不会影响到对方,这才是完美的根据单一职责原则编写出来的代码。

类

类这个看了一些资料都说没法硬性要求一定按单一职责原则分,或者说类的职责可大可小,没有很明确的像上面接口那样按照单一职责原则分就很清晰也很有道理。
设想一下这个场景:我们要实现一个用户注册、登录、注销操作,可以像如下 2 种实现方式
代码:SrpOfClass.java

第一种实现方式

从用户的角度考虑,这些操作都是用户的行为,可以放在一个统一的类 UserBiz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class UserBiz {

public boolean register(User user){
// 注册操作
return true;
}

public boolean login(User user) {
// 登录操作
return true;
}

public boolean logout(User user) {
// 注销操作
return true;
}

}

第二种实现方式

有人又说,不是说单一职责么?从业务操作考虑,需要把注册、登录、注销分开

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
class UserRegisterBiz {

public boolean register(User user){
// 注册操作
return true;
}

}

class UserLoginBiz {

public boolean login(User user) {
// 登录操作
return true;
}

}

class UserLogoutBiz {

public boolean logout(User user) {
// 注销操作
return true;
}

}

感觉像是在抬杠,其实这个没有好坏之分,根据具体业务具体分析,你说你的登录、注册、注销操作代码很多,需要分开,那就分开,无可厚非。

好处

  1. 类的复杂性降低,实现什么职责都有清晰明确的定义
  2. 可读性提高,复杂性降低,那当然可读性提高了
  3. 可维护性提高,可读性提高,那当然更容易维护了
  4. 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助
    (来自《设计模式之禅》)

总结

这个单一职责原则,目的就是提高代码的可维护性、可读性、扩展性,如果为了单一职责而破坏了这 3 个特性,可能会得不偿失。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章


LieBrother

wechatjpg
1…91011…24
LieBrother

LieBrother

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

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