控制反转、依赖反转、依赖注入、KISS、YAGNI原则

** 依赖**

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) 要不要做

不要做过度设计

  • 无用功能的设计
  • 无用第三方库的导入

你可能感兴趣的:(控制反转、依赖反转、依赖注入、KISS、YAGNI原则)