** 依赖**
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class A {
private B b;
public A() {
this.b = new B();
}
}
public class A {
public void func(B b) { ... }
}
控制反转 IOC (Inversion Of Control)
框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。
依赖注入 DI (Dependency Injection)
把有依赖关系的类放到容器中,解析出这些类的实例,就是依赖注入。目的是实现类的解耦。
依赖倒置 DIP (Dependence Inversion Principle)
是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象。
抽象不应该依赖细节,细节应该依赖抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并且能够降低修改程序所造成的风险。
// 重生之小明 - 进击码农
// MARK: **- 小明和他的手机**
class Persion {
}
//从前有个人叫小明小明有三大爱好,逛知乎、玩王者农药和抢微信红包
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
func read() {
//逛知乎
}
func play(){
//玩农药
}
func grab() {
//抢红包
}
}
// 但是,小明作为一个人类,没有办法仅靠自己就能实现以上的功能,他必须依赖一部手机,
class IPhone6 {
func read(name: String) {
print("\(name)打开了知乎然后编了一个故事")
}
func play(name: String) {
print("\(name)打开了王者农药并送起了人头")
}
func grab(name: String){
print("\(name)开始抢红包却只抢不发")
}
}
// 小明非常珍惜自己的新手机,每天把它牢牢控制在手心里,所以小明变成了这个样子
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
func read() {
//逛知乎
let iphone6 = IPhone6()
iphone6.read(name: self.name)
}
func play(){
//玩农药
let iphone6 = IPhone6()
iphone6.grab(name: self.name)
}
func grab() {
//抢红包
let iphone6 = IPhone6()
iphone6.grab(name: self.name)
}
}
// 今天是周六,小明不用上班,于是他起床,并依次逛起了知乎,玩王者农药,并抢了个红包。
let xiaoming = XiaoMing()
xiaoming.read()
xiaoming.play()
xiaoming.grab()
// 这个时候,我们可以在命令行里看到输出如下
/*
小明打开了知乎然后编了一个故事
小明打开了王者农药并送起了人头
小明开始抢红包却只抢不发
*/
//这一天,小明过得很充实,他觉得自己是世界上最幸福的人。
// MARK: - 第二章: 小明的快乐与忧伤
/*
小明和他的手机曾一起度过了一段美好的时光,一到空闲时刻,他就抱着手机,逛知乎,刷微博,玩游戏,他觉得自己根本不需要女朋友,只要有手机在身边,就满足了。可谁能想到,一次次地系统更新彻底打碎了他的梦想,他的手机变得越来越卡顿,电池的使用寿命也越来越短,一直到某一天的寒风中,他的手机终于耐不住寒冷,头也不回地关了机。小明很忧伤,他意识到,自己要换手机了。为了能获得更好的使用体验,小明一咬牙,剁手了一台iphoneX,这部手机铃声很大,电量很足,还能双卡双待,小明很喜欢,但是他遇到一个问题,就是他之前过度依赖了原来那一部iPhone6,他们之间已经深深耦合在一起了,如果要换手机,他就要拿起刀来改造自己,把自己体内所有方法中的iphone6 都换成 iphoneX。
漫长的改造过程经历了漫长的改造过程,小明终于把代码中的 iphone6 全部换成了 iphoneX。虽然很辛苦,但是小明觉得他是快乐的。
*/
class IphoneX {
func read(name: String) {
print("\(name)打开了知乎然后编了一个故事")
}
func play(name: String) {
print("\(name)打开了王者农药并送起了人头")
}
func grab(name: String){
print("\(name)开始抢红包却只抢不发")
}
func isBroken() -> Bool {
return true
}
}
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
func read() {
//逛知乎
let iphoneX = IphoneX()
iphone6.read(name: self.name)
}
func play(){
//玩农药
let iphoneX = IphoneX()
iphone6.grab(name: self.name)
}
func grab() {
//抢红包
let iphoneX = IphoneX()
iphone6.grab(name: self.name)
}
}
/*
于是小明开开心心地带着手机去上班了,并在回来的路上被小偷偷走了。为了应急,小明只好重新使用那部刚刚被遗弃的iphone6,但是一想到那漫长的改造过程,小明的心里就说不出的委屈,他觉得自己过于依赖手机了,为什么每次手机出什么问题他都要去改造他自己,这不仅仅是过度耦合,简直是本末倒置,他向天空大喊,我不要再控制我的手机了。
天空中的造物主,也就是作为程序员的我,听到了他的呐喊,我告诉他,你不用再控制你的手机了,交给我来管理,把控制权交给我。这就叫做控制反转。
*/
// MARK: - 第三章:造物主的智慧
/*
小明听到了我的话,他既高兴,又有一点害怕,他跪下来磕了几个头,虔诚地说到:“原来您就是传说中的造物主,巴格梅克上神。我听到您刚刚说了 控制反转 四个字,就是把手机的控制权从我的手里交给你,但这只是您的想法,是一种思想罢了,要用什么办法才能实现控制反转,又可以让我继续使用手机呢?”
“呵“,身为造物主的我在表现完不屑以后,扔下了八个大字,“ 依赖注入,依赖倒置!”
接下来,伟大的我开始对小明进行惨无人道的改造,如下
*/
protocol Iphone {
func read(name: String)
func play(name: String)
func grab(name: String)
func isBroken() -> Bool
}
class IPhone6: Iphone {
func isBroken() -> Bool {
return false
}
func read(name: String) {
print("\(name)打开了知乎然后编了一个故事")
}
func play(name: String) {
print("\(name)打开了王者农药并送起了人头")
}
func grab(name: String){
print("\(name)开始抢红包却只抢不发")
}
}
class IphoneX: Iphone {
func read(name: String) {
print("\(name)打开了知乎然后编了一个故事")
}
func play(name: String) {
print("\(name)打开了王者农药并送起了人头")
}
func grab(name: String){
print("\(name)开始抢红包却只抢不发")
}
func isBroken() -> Bool {
return true
}
}
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
private var iphone: Iphone? = nil
init(phone: Iphone) {
self.iphone = phone
}
func read() {
//逛知乎
iphone?.read(name: self.name)
}
func play(){
//玩农药
iphone?.grab(name: self.name)
}
func grab() {
//抢红包
iphone?.grab(name: self.name)
}
}
// MARK: - 第四章:小明的感悟
//小明的生活开始变得简单了起来,而他把省出来的时间都用来写笔记了,他在笔记本上这样写到
/**
我曾经有很强的控制欲,过度依赖于我的手机,导致我和手机之间耦合程度太高,只要手机出现一点点问题,我都要改造我自己,这实在是既浪费时间又容易出问题。自从我把控制权交给了造物主,他每天在唤醒我以前,就已经替我选好了手机,我只要按照平时一样玩手机就可以了,根本不用关心是什么手机。即便手机出了问题,也可以由造物主直接搞定,不需要再改造我自己了,我现在买了七部手机,都交给了造物主,每天换一部,美滋滋!我也从其中获得了这样的感悟: 如果一个类A 的功能实现需要借助于类B,那么就称类B是类A的依赖,如果在类A的内部去实例化类B,那么两者之间会出现较高的耦合,一旦类B出现了问题,类A也需要进行改造,如果这样的情况较多,每个类之间都有很多依赖,那么就会出现牵一发而动全身的情况,程序会极难维护,并且很容易出现问题。要解决这个问题,就要把A类对B类的控制权抽离出来,交给一个第三方去做,把控制权反转给第三方,就称作控制反转(IOC Inversion Of Control)。控制反转是一种思想,是能够解决问题的一种可能的结果,而依赖倒置 (Dependence Inversion Principle)把控制权抽离出来,而依赖注入(Dependency Injection)就是其最典型的实现方法 。由第三方(我们称作IOC容器)来控制依赖,把他通过构造函数、属性或者工厂模式等方法,注入到类A内,这样就极大程度的对类A和类B进行了解耦。
*/
KISS (Keep it Simple and Stupid) 怎么做
让代码尽可能简单,目的是保持代码可读和可维护性
- 代码行数越少就越“简单”吗?
// 第一种实现方式: 使用正则表达式
public boolean isValidIpAddressV1(String ipAddress) {
if (StringUtils.isBlank(ipAddress)) return false;
String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
return ipAddress.matches(regex);
}
// 第二种实现方式: 使用现成的工具类
public boolean isValidIpAddressV2(String ipAddress) {
if (StringUtils.isBlank(ipAddress)) return false;
String[] ipUnits = StringUtils.split(ipAddress, '.');
if (ipUnits.length != 4) {
return false;
}
for (int i = 0; i < 4; ++i) {
int ipUnitIntValue;
try {
ipUnitIntValue = Integer.parseInt(ipUnits[i]);
} catch (NumberFormatException e) {
return false;
}
if (ipUnitIntValue < 0 || ipUnitIntValue > 255) {
return false;
}
if (i == 0 && ipUnitIntValue == 0) {
return false;
}
}
return true;
}
// 第三种实现方式: 不使用任何工具类
public boolean isValidIpAddressV3(String ipAddress) {
char[] ipChars = ipAddress.toCharArray();
int length = ipChars.length;
int ipUnitIntValue = -1;
boolean isFirstUnit = true;
int unitsCount = 0;
for (int i = 0; i < length; ++i) {
char c = ipChars[i];
if (c == '.') {
if (ipUnitIntValue < 0 || ipUnitIntValue > 255) return false;
if (isFirstUnit && ipUnitIntValue == 0) return false;
if (isFirstUnit) isFirstUnit = false;
ipUnitIntValue = -1;
unitsCount++;
continue;
}
if (c < '0' || c > '9') {
return false;
}
if (ipUnitIntValue == -1) ipUnitIntValue = 0;
ipUnitIntValue = ipUnitIntValue * 10 + (c - '0');
}
if (ipUnitIntValue < 0 || ipUnitIntValue > 255) return false;
if (unitsCount != 3) return false;
return true;
}
代码逻辑复杂就违背 KISS 原则吗?
/**
刚刚我们提到,并不是代码行数越少就越“简单”,还要考虑逻辑复杂度、实现难度、代码的可读性等。那如果一段代码的逻辑复杂、实现难度大、可读性也不太好,是不是就一定违背 KISS 原则呢?
*/
// KMP algorithm: a, b 分别是主串和模式串;n, m 分别是主串和模式串的长度。
public static int kmp(char[] a, int n, char[] b, int m) {
int[] next = getNexts(b, m);
int j = 0;
for (int i = 0; i < n; ++i) {
while (j > 0 && a[i] != b[j]) { // 一直找到 a[i] 和 b[j]
j = next[j - 1] + 1;
}
if (a[i] == b[j]) {
++j;
}
if (j == m) { // 找到匹配模式串的了
return i - m + 1;
}
}
return -1;
}
// b 表示模式串,m 表示模式串的长度
private static int[] getNexts(char[] b, int m) {
int[] next = new int[m];
next[0] = -1;
int k = -1;
for (int i = 1; i < m; ++i) {
while (k != -1 && b[k + 1] != b[i]) {
k = next[k];
}
if (b[k + 1] == b[i]) {
++k;
}
next[i] = k;
}
return next;
}
/**
KMP 算法以快速高效著称。当我们需要处理长文本字符串匹配问题(几百 MB 大小文本内容的匹配),或者字符串匹配是某个产品的核心功能(比如 Vim、Word 等文本编辑器),又或者字符串匹配算法是系统性能瓶颈的时候,我们就应该选择尽可能高效的 KMP 算法。而 KMP 算法本身具有逻辑复杂、实现难度大、可读性差的特点。本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。
不过,平时的项目开发中涉及的字符串匹配问题,大部分都是针对比较小的文本。在这种情况下,直接调用编程语言提供的现成的字符串匹配函数就足够了。如果非得用 KMP 算法、BM 算法来实现字符串匹配,那就真的违背 KISS 原则了。也就是说,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。
*/
如何写出满足 KISS 原则的代码?
不要使用同事可能不懂的技术来实现代码;
不要重复造轮子,要善于使用已经有的工具类库;
不要过度优化。
YAGNI (You Ain’t Gonna Need It) 要不要做
不要做过度设计
- 无用功能的设计
- 无用第三方库的导入
网友评论