ARTS 第56周

ARTS (第56周)

常用的设计原则。
SOLID 原则 -SRP 单一职责原则
SOLID 原则 -OCP 开闭原则
SOLID 原则 -LSP 里式替换原则
SOLID 原则 -ISP 接口隔离原则
SOLID 原则 -DIP 依赖倒置原则
DRY 原则、KISS 原则、YAGNI 原则、LOD 法则

Algorithm 算法

表示数值的字符串

1
2
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

解法 逐个字符判断

将每个字符的情况都考虑进去

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
public boolean isNumeric(char[] str) {
if (str == null || str.length == 0)
return false;
boolean e = false;// 是否有e 只能有一个
boolean dot = false;// 是否有小数点 只能有一个
for (int i = 0; i < str.length; i++) {
char c = str[i];
if (c == '+' || c == '-') {
if (i < str.length - 1 && str[i + 1] < '0' && str[i + 1] > '9')
return false;
if (i == 0 || str[i - 1] == 'e' || str[i - 1] == 'E')
continue;
else
return false;
} else if (c == 'e' || c == 'E') {
if (e)
return false;
// 不能开头或结尾
if (i == 0 || i == str.length - 1)
return false;
e = true;
} else if (c == '.') {
if (dot || e) {
// 可以是小数点 E整数 但是不能是有了E了 还有小数点
return false;
}
// 不能开头或结尾
if (i == 0 || i == str.length - 1)
return false;
dot = true;
}
// 其余字符
else if (c < '0' || c > '9')
return false;

}
return true;
}

字符流中第一个不重复的字符

1
2
3
4
5
题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

解法 索引每个字符存在的位置和数量。然后快速查找。

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

import java.util.LinkedList;
public class Solution {
LinkedList<Character> list = new LinkedList<Character>();
int[] index = new int[256];

// Insert one char from stringstream
public void Insert(char ch) {
list.add(ch);
index[ch]++;
}

// return the first appearence once char in current stringstream
public char FirstAppearingOnce() {

for (Character c : list) {
//char idx = c;
if (index[c] == 1)
return c;
}
return '#';
}
}

链表中环的入口结点

1
2
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null
https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

解法1 暴力法

记录遍历过的节点,发现某个节点遍历过的话,这个节点就是环的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
public ListNode EntryNodeOfLoop_(ListNode pHead) {
if (pHead == null)
return null;
LinkedList<ListNode> list = new LinkedList<ListNode>();
while (pHead != null) {
// 即获取首个遍历了2次的节点
if (list.contains(pHead))
return pHead;
list.add(pHead);
pHead = pHead.next;
}
return pHead;
}

解法2 快慢指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public ListNode EntryNodeOfLoop(ListNode pHead) {
if (pHead == null)
return null;
ListNode p1 = pHead;// 慢指针
ListNode p2 = pHead;// 快指针
while (p2 != null && p2.next != null) {
p2 = p2.next.next;// 走两步
p1 = p1.next;// 走一步
if (p2 == p1) {// 有环
// 快指针从头走 每次走一步即可
// 因为快指针每次走的步数是慢指针的两倍
// 慢指针再走头节点到发现环的节点的距离就能找到了
p2 = pHead;
while (p1 != p2) {
p2 = p2.next;
p1 = p1.next;
}
return p1;
}
}
return null;
}

Review 英文文章

https://spring.io/guides/gs/serving-web-content/

官方文档里的spring mvc搭建

Tip 技巧

接口隔离原则

理解“接口隔离原则”的重点是理解其中的“接口”二字。这里有三种不同的理解。如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。如果把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。如果把“接口”理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

出处:极客时间《设计模式之美》(作者:王争)

https://time.geekbang.org/column/intro/250

依赖注入

我们用一句话来概括就是:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。

出处:极客时间《设计模式之美》(作者:王争)

https://time.geekbang.org/column/intro/250

依赖反转原则(DIP)

High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.
大概意思就是:高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。
个人见解:从java spring框架的角度,通俗点来说呢,就是我们在需要依赖的时候,应该依赖接口而不应该依赖实现类,也即是spring所鼓励的接口编程。

而控制反转,就是比如框架提供了一个可扩展的代码骨架(如接口),用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码(如实现接口),就可以利用框架来驱动整个程序流程的执行。

出处:极客时间《设计模式之美》(作者:王争)

[https://time.geekbang.org/column/intro/250

KISS 原则和YAGNI原则

翻译成中文就是:尽量保持简单。
KISS 原则就是保持代码可读和可维护的重要手段。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。即便出现 bug,修复起来也比较简单。

YAGNI 原则跟 KISS 原则并非一回事儿。KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是“要不要做”的问题(当前不需要的就不要做)。

DRY 原则(Don’t Repeat Yourself)

中文直译为:不要重复自己。
三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。除此之外,代码执行重复也算是违反 DRY 原则。

LOD原则

迪米特法则的英文翻译是:Law of Demeter
它还有另外一个更加达意的名字,叫作最小知识原则。
不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中的“有限知识”)。

Share 分享

https://time.geekbang.org/column/intro/250 极客时间《设计模式之美》17~22章

https://www.jianshu.com/p/c9186119f3d1 JMH 简单入门(JAVA 微基准测试套件