作者简介:

       姜海强:闷骚码农,互联网行业摸爬滚打数余载,先后担任中国体育直播TV主程、网信集团先锋支付架构师、奇虎360服务器端资深开发。热爱技术,喜欢分享,热衷领域:PHP/Golang语言、面向对象设计模式、Redis、Yaf、Yii2、微服务等。

视频课程

yaf+yar微服务-腾讯课堂
yaf+yar微服务-51CTO学院
CSDN学院

Github

个人主页
swoole-boot
roach
roach-orm

QQ群:

姜海强的QQ群

公众号:

360tryst公众号

设计模式

1.什么是模式

设计模式是对某些典型易变问题的特定解决方案,这些问题和解决方案经过分类总结,并且为了方便交流给每个解决方案都起了特定的名字。

模式是为了解决变化的问题,将变化的问题进行封装,让变化单独变化而互不影响,以达到项目系统的扩展性,模式是一种更高层次的代码复用。

设想一下,假如我们的项目是一次性项目,一旦发布,以后再也不会修改,那么我们完全没有必要使用设计模式,反而使用了设计模式会增加系统的复杂度,但是一次性的项目在我们实际工作环境中是极少的,如果实际工作环境中都是一次项目,那个时候我们开发人员都应该转行了。

2.为什么要使用模式

  • 每一个设计模式都是为了解决一类特定的问题而存在,是一种更高层级的复用

  • 每一个设计模式都有一个特定的名字,团队协作中,既可以减少沟通成本,同时也可以保证项目质量

  • 模式有助于开发人员提高解决问题的思考层次,能够让我们的系统更容易扩展

3.六大原则

前辈们经过长期经验积累总结了许多设计模式,如单例模式、工厂模式、享元模式、策略模式、观察者模式等等,这些模式都是为了解决某一类特定的问题而设计,但是我们在日常编码过程中可以把这些模式作为一种参考即可,我们在编码过程中满足模式的原则即认为是好的代码。

模式有以下六大原则

名称 解释
单一职责 要存在多于一个导致类变更的原因,通俗的说,即一个类只负责一项职责
里式代换 子类可以扩展父类的功能,但不能改变父类原有的功能
依赖倒置 高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象
接口隔离 客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上
迪米特法则 一个对象应该对其他对象保持最少的了解
开闭原则 一个软件实体如类、模块和函数应该对扩展开放,对修改关闭

3.1 单一职责

单一职责看似简单,其实并不简单,这个原则告诉了我们划分一个类功能的标准,下面我们看一段常见的代码片段

  1. <?php
  2. /**
  3. * Class Login
  4. * @datetime 2020/7/12 4:13 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. abstract class Login
  9. {
  10. /**微信appId
  11. * @var string
  12. * @datetime 2020/7/12 4:11 PM
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. public $wxAppid;
  17. /**微信密钥
  18. * @var string
  19. * @datetime 2020/7/12 4:11 PM
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public $wxAppSecret;
  24. /**微信登录
  25. * @return mixed
  26. * @datetime 2020/7/12 4:12 PM
  27. * @author roach
  28. * @email jhq0113@163.com
  29. */
  30. abstract public function wxLogin();
  31. /**手机号验证码登录
  32. * @return mixed
  33. * @datetime 2020/7/12 4:12 PM
  34. * @author roach
  35. * @email jhq0113@163.com
  36. */
  37. abstract public function phoneLogin();
  38. }

以上例程代码是一个用户登录的代码片段,为了演示方便作者把wxLoginphoneLogin两个方法实现细节进行了屏蔽。

以上代码有什么问题吗?不都是这么做登录的吗?

从模式原则来分析,假如微信登录实现细节发生修改,我们要修改Login类,手机号验证码登录实现细节发生修改,我们同样要修改Login类,如果我们要增加一种QQ授权登录方式,同样我们要修改Login

在模式的理论中,只要发生修改就会有风险,那么我们怎么解决这个问题呢?按照单一职责原则,作者把代码做了如下拆分优化

  1. <?php
  2. /**
  3. * Class Login
  4. * @datetime 2020/7/12 4:13 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. abstract class WxLogin
  9. {
  10. /**微信appId
  11. * @var string
  12. * @datetime 2020/7/12 4:11 PM
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. public $wxAppid;
  17. /**微信密钥
  18. * @var string
  19. * @datetime 2020/7/12 4:11 PM
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public $wxAppSecret;
  24. /**
  25. * @param array $params
  26. * @return mixed
  27. * @datetime 2020/7/12 4:20 PM
  28. * @author roach
  29. * @email jhq0113@163.com
  30. */
  31. abstract public function login($params = []);
  32. }
  33. /**
  34. * Class PhoneLogin
  35. * @datetime 2020/7/12 4:20 PM
  36. * @author roach
  37. * @email jhq0113@163.com
  38. */
  39. abstract class PhoneLogin
  40. {
  41. /**
  42. * @param array $params
  43. * @return mixed
  44. * @datetime 2020/7/12 4:20 PM
  45. * @author roach
  46. * @email jhq0113@163.com
  47. */
  48. abstract public function login($params = []);
  49. }

作者将Login类一分为二,微信登录的appId等依赖也放到各自类中,这样调用端如果想实现登录功能选择调用类就可以了。

现在我们分析,假如微信登录实现要发生修改,我们直接修改WxLogin类就可以了,同样的,假如手机号验证码登录实现要发生修改,我们只需要修改PhoneLogin类就可以了;假如我们要增加一种QQ授权登录的方式,此时我们增加一个QqLogin类就可以了。

3.2 里式代换

里式代换原则强调,我们能够扩展基类的原有功能,但不能改变基类原有的功能,这样很容易出现问题,下面还是看一段示例代码

  1. <?php
  2. /**
  3. * Class Model
  4. * @datetime 2020/7/12 4:30 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. class Model
  9. {
  10. /**
  11. * @var \PDO
  12. * @datetime 2020/7/12 4:29 PM
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. private $_db;
  17. /**
  18. * @param \PDO $db
  19. * @datetime 2020/7/12 4:45 PM
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public function setDb(\PDO $db)
  24. {
  25. $this->_db = $db;
  26. }
  27. /**
  28. * @return \PDO
  29. * @datetime 2020/7/12 4:32 PM
  30. * @author roach
  31. * @email jhq0113@163.com
  32. */
  33. public function getDb()
  34. {
  35. if(is_null($this->_db)) {
  36. $this->setDb(new \PDO('mysql:host=127.0.0.1;port=3306;dbname=roach', 'roach', 'roach'));
  37. }
  38. return $this->_db;
  39. }
  40. /**
  41. * @param null $name
  42. * @return string
  43. * @datetime 2020/7/12 4:36 PM
  44. * @author roach
  45. * @email jhq0113@163.com
  46. */
  47. public function lastInsertId($name = null)
  48. {
  49. return $this->_db->lastInsertId($name);
  50. }
  51. }
  52. /**
  53. * Class UserModel
  54. * @datetime 2020/7/12 4:35 PM
  55. * @author roach
  56. * @email jhq0113@163.com
  57. */
  58. class UserModel extends Model
  59. {
  60. /**
  61. * @return PDO|string
  62. * @datetime 2020/7/12 4:35 PM
  63. * @author roach
  64. * @email jhq0113@163.com
  65. */
  66. public function getDb()
  67. {
  68. return MyModel::db();
  69. }
  70. }
  71. /**
  72. * Class MyModel
  73. * @datetime 2020/7/12 4:34 PM
  74. * @author roach
  75. * @email jhq0113@163.com
  76. */
  77. class MyModel
  78. {
  79. /**
  80. * @var \PDO
  81. * @datetime 2020/7/12 4:33 PM
  82. * @author roach
  83. * @email jhq0113@163.com
  84. */
  85. private static $_db;
  86. /**
  87. * @return PDO
  88. * @datetime 2020/7/12 4:34 PM
  89. * @author roach
  90. * @email jhq0113@163.com
  91. */
  92. public static function db()
  93. {
  94. if(is_null(self::$_db)) {
  95. self::$_db = new \PDO('mysql:host=127.0.0.1;port=3306;dbname=roach', 'roach', 'roach');
  96. }
  97. return self::$_db;
  98. }
  99. }

以上代码也是真实遇到的,分析以上代码,在多人并行开发的团队,一个开发发现Model类的db并非真正的单例,于是这个开发在不改变业务调用代码的基础上,增加了MyModel类实现了约定单例,但是在实现UserModelgetDb方法的时候,改变了基类Model原有的getDb方法给_db私有属性赋值,这样导致基类lastInsertId方法不能使用,此时如果我们使用基类可以正常调用lastInsertId方法,但是使用子类UserModel调用lastInsertId就会发生异常。

以上代码违背了里式代换原则,那么我们应该如何修改呢?作者对UserModel进行了如下优化

  1. <?php
  2. /**
  3. * Class UserModel
  4. * @datetime 2020/7/12 4:35 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. class UserModel extends Model
  9. {
  10. /**
  11. * @return \PDO
  12. * @datetime 2020/7/12 4:35 PM
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. public function getDb()
  17. {
  18. $this->setDb(MyModel::db());
  19. return parent::getDb();
  20. }
  21. }

这样修改后保证了单例,和里式代换

3.3 依赖倒置

依赖倒置的思想是个很经典扩展性思维,开发人员如果能够深刻理解依赖倒置原则,将有很大层次的提升,还是看如下代码

  1. <?php
  2. /**
  3. * Class FileLog
  4. * @datetime 2020/7/12 6:09 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. class FileLog
  9. {
  10. /**
  11. * @var string
  12. * @datetime 2020/7/12 6:05 PM
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. protected $_logFile;
  17. /**
  18. * @param $fileName
  19. * @datetime 2020/7/12 6:06 PM
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public function setLogFile($fileName)
  24. {
  25. $this->_logFile = $fileName;
  26. }
  27. /**
  28. * @param string $message
  29. * @param array $context
  30. * @param string $leftPlace
  31. * @param string $rightPlace
  32. * @return string
  33. * @datetime 2019/8/30 18:00
  34. * @author roach
  35. * @email jhq0113@163.com
  36. */
  37. static public function interpolate($message, array $context = [], $leftPlace='{', $rightPlace='}')
  38. {
  39. if(empty($context)) {
  40. return $message;
  41. }
  42. $replace = [];
  43. foreach ($context as $key => $val) {
  44. $replace[$leftPlace . $key . $rightPlace] = $val;
  45. }
  46. return strtr($message, $replace);
  47. }
  48. /**
  49. * @param string $level
  50. * @param string $message
  51. * @param array $params
  52. * @datetime 2020/7/12 6:07 PM
  53. * @author roach
  54. * @email jhq0113@163.com
  55. */
  56. public function log($level, $message, $params = [])
  57. {
  58. $message = self::interpolate($message, $params);
  59. $message = json_encode([
  60. 'datetime' => date('Y-m-d H:i:s'),
  61. 'level' => $level,
  62. 'msg' => $message,
  63. 'clientIp' => $_SERVER['REMOTE_ADDR'],
  64. 'url' => $_SERVER['REQUEST_URI'],
  65. 'method' => $_SERVER['REQUEST_METHOD'],
  66. 'host' => $_SERVER['HTTP_HOST']
  67. ], JSON_UNESCAPED_UNICODE);
  68. file_put_contents($this->_logFile, $message.PHP_EOL, FILE_APPEND | LOCK_EX);
  69. }
  70. }
  71. /**
  72. * Class DbLog
  73. * @datetime 2020/7/12 6:11 PM
  74. * @author roach
  75. * @email jhq0113@163.com
  76. */
  77. class DbLog
  78. {
  79. /**
  80. * @var \PDO
  81. * @datetime 2020/7/12 6:11 PM
  82. * @author roach
  83. * @email jhq0113@163.com
  84. */
  85. protected $_pdo;
  86. /**
  87. * @param \PDO $db
  88. * @datetime 2020/7/12 6:12 PM
  89. * @author roach
  90. * @email jhq0113@163.com
  91. */
  92. public function setDb(\PDO $db)
  93. {
  94. $this->_pdo = $db;
  95. }
  96. /**
  97. * @param string $message
  98. * @param array $context
  99. * @param string $leftPlace
  100. * @param string $rightPlace
  101. * @return string
  102. * @datetime 2019/8/30 18:00
  103. * @author roach
  104. * @email jhq0113@163.com
  105. */
  106. static public function interpolate($message, array $context = [], $leftPlace='{', $rightPlace='}')
  107. {
  108. if(empty($context)) {
  109. return $message;
  110. }
  111. $replace = [];
  112. foreach ($context as $key => $val) {
  113. $replace[$leftPlace . $key . $rightPlace] = $val;
  114. }
  115. return strtr($message, $replace);
  116. }
  117. /**
  118. * @param string $level
  119. * @param string $message
  120. * @param array $params
  121. * @return int
  122. * @datetime 2020/7/12 6:16 PM
  123. * @author roach
  124. * @email jhq0113@163.com
  125. */
  126. public function log($level, $message, $params = [])
  127. {
  128. $message = self::interpolate($message, $params);
  129. $stmt = $this->_pdo->prepare('INSERT INTO `logs`(`datetime`, `level`, `msg`, `clientIp`, `url`, `method`, `host`)VALUES(?,?,?,?,?,?,?)');
  130. $stmt->execute([
  131. date('Y-m-d H:i:s'),
  132. $level,
  133. $message,
  134. $_SERVER['REMOTE_ADDR'],
  135. $_SERVER['REQUEST_URI'],
  136. $_SERVER['REQUEST_METHOD'],
  137. $_SERVER['HTTP_HOST']
  138. ]);
  139. return $stmt->rowCount();
  140. }
  141. }
  142. /**
  143. * Class LogFactory
  144. * @datetime 2020/7/12 6:21 PM
  145. * @author roach
  146. * @email jhq0113@163.com
  147. */
  148. class Logger
  149. {
  150. /**
  151. * @param string $loggerName
  152. * @param string $level
  153. * @param string $message
  154. * @param array $params
  155. * @return int|void
  156. * @datetime 2020/7/12 6:25 PM
  157. * @author roach
  158. * @email jhq0113@163.com
  159. */
  160. public function log($loggerName, $level, $message, $params = [])
  161. {
  162. if($loggerName === 'db') {
  163. $logger = new DbLog();
  164. $logger->setDb(new \PDO('mysql:host=127.0.0.1;port=3306;dbname=roach', 'roach', 'roach'));
  165. return $logger->log($level, $message, $params);
  166. }
  167. $logger = new FileLog();
  168. $logger->setLogFile('/tmp/logs/'.date('Y').'/'.date('m-d').'.log');
  169. return $logger->log($level, $message, $params);
  170. }
  171. }

以上代码示例中,Logger类的log方法通过传入参数$loggerName来决定用哪个类去记录日志,这样Logger类直接依赖FileLogDbLog类,当我们再增加一个KafkaLog类打日志时,Logger同样需要依赖KafkaLog类,这样Logger类就需要发生修改,在模式领域,这样的代码认为不可以扩展,那么怎么优化呢?

  1. <?php
  2. /**
  3. * Class ILog
  4. * @datetime 2020/7/12 6:32 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. abstract class ILog
  9. {
  10. /**
  11. * @param string $message
  12. * @param array $context
  13. * @param string $leftPlace
  14. * @param string $rightPlace
  15. * @return string
  16. * @datetime 2019/8/30 18:00
  17. * @author roach
  18. * @email jhq0113@163.com
  19. */
  20. static public function interpolate($message, array $context = [], $leftPlace='{', $rightPlace='}')
  21. {
  22. if(empty($context)) {
  23. return $message;
  24. }
  25. $replace = [];
  26. foreach ($context as $key => $val) {
  27. $replace[$leftPlace . $key . $rightPlace] = $val;
  28. }
  29. return strtr($message, $replace);
  30. }
  31. /**
  32. * @param string $level
  33. * @param string $message
  34. * @param array $params
  35. * @return mixed
  36. * @datetime 2020/7/12 6:30 PM
  37. * @author roach
  38. * @email jhq0113@163.com
  39. */
  40. abstract public function log($level, $message, $params = []);
  41. }
  42. /**
  43. * Class FileLog
  44. * @datetime 2020/7/12 6:09 PM
  45. * @author roach
  46. * @email jhq0113@163.com
  47. */
  48. class FileLog extends ILog
  49. {
  50. /**
  51. * @var string
  52. * @datetime 2020/7/12 6:05 PM
  53. * @author roach
  54. * @email jhq0113@163.com
  55. */
  56. protected $_logFile;
  57. /**
  58. * @param $fileName
  59. * @datetime 2020/7/12 6:06 PM
  60. * @author roach
  61. * @email jhq0113@163.com
  62. */
  63. public function setLogFile($fileName)
  64. {
  65. $this->_logFile = $fileName;
  66. }
  67. /**
  68. * @param string $level
  69. * @param string $message
  70. * @param array $params
  71. * @datetime 2020/7/12 6:07 PM
  72. * @author roach
  73. * @email jhq0113@163.com
  74. */
  75. public function log($level, $message, $params = [])
  76. {
  77. $message = self::interpolate($message, $params);
  78. $message = json_encode([
  79. 'datetime' => date('Y-m-d H:i:s'),
  80. 'level' => $level,
  81. 'msg' => $message,
  82. 'clientIp' => $_SERVER['REMOTE_ADDR'],
  83. 'url' => $_SERVER['REQUEST_URI'],
  84. 'method' => $_SERVER['REQUEST_METHOD'],
  85. 'host' => $_SERVER['HTTP_HOST']
  86. ], JSON_UNESCAPED_UNICODE);
  87. file_put_contents($this->_logFile, $message.PHP_EOL, FILE_APPEND | LOCK_EX);
  88. }
  89. }
  90. /**
  91. * Class DbLog
  92. * @datetime 2020/7/12 6:11 PM
  93. * @author roach
  94. * @email jhq0113@163.com
  95. */
  96. class DbLog extends ILog
  97. {
  98. /**
  99. * @var \PDO
  100. * @datetime 2020/7/12 6:11 PM
  101. * @author roach
  102. * @email jhq0113@163.com
  103. */
  104. protected $_pdo;
  105. /**
  106. * @param \PDO $db
  107. * @datetime 2020/7/12 6:12 PM
  108. * @author roach
  109. * @email jhq0113@163.com
  110. */
  111. public function setDb(\PDO $db)
  112. {
  113. $this->_pdo = $db;
  114. }
  115. /**
  116. * @param string $level
  117. * @param string $message
  118. * @param array $params
  119. * @return int
  120. * @datetime 2020/7/12 6:16 PM
  121. * @author roach
  122. * @email jhq0113@163.com
  123. */
  124. public function log($level, $message, $params = [])
  125. {
  126. $message = self::interpolate($message, $params);
  127. $stmt = $this->_pdo->prepare('INSERT INTO `logs`(`datetime`, `level`, `msg`, `clientIp`, `url`, `method`, `host`)VALUES(?,?,?,?,?,?,?)');
  128. $stmt->execute([
  129. date('Y-m-d H:i:s'),
  130. $level,
  131. $message,
  132. $_SERVER['REMOTE_ADDR'],
  133. $_SERVER['REQUEST_URI'],
  134. $_SERVER['REQUEST_METHOD'],
  135. $_SERVER['HTTP_HOST']
  136. ]);
  137. return $stmt->rowCount();
  138. }
  139. }
  140. /**
  141. * Class LogFactory
  142. * @datetime 2020/7/12 6:21 PM
  143. * @author roach
  144. * @email jhq0113@163.com
  145. */
  146. class Logger
  147. {
  148. /**
  149. * @param ILog $logger
  150. * @param string $level
  151. * @param string $message
  152. * @param array $params
  153. * @return mixed
  154. * @datetime 2020/7/12 6:32 PM
  155. * @author roach
  156. * @email jhq0113@163.com
  157. */
  158. public function log(ILog $logger, $level, $message, $params = [])
  159. {
  160. return $logger->log($level, $message, $params);
  161. }
  162. }

我们可以看到,在优化过程中,我们增加了ILog抽象类,并增加了抽象方法logLogger类中log方法不再依赖$loggerName,改为依赖抽象类ILog,这样,当我们增加KafkaLog类打日志时,Logger类不需要再做任何修改。

这就是典型的依赖倒置思想。

3.4 接口隔离

我们刚刚学会了依赖倒置,高层模块不应该依赖于低层模块儿,二者应该依赖其抽象。接口隔离告诉我们,这个抽象要有个度,应该建立在最小接口上。

看如下代码

  1. <?php
  2. /**
  3. * Interface IOrderAndLogin
  4. * @datetime 2020/7/12 6:45 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. interface IOrderAndLogin
  9. {
  10. /**
  11. * @param array $params
  12. * @return mixed
  13. * @datetime 2020/7/12 6:44 PM
  14. * @author roach
  15. * @email jhq0113@163.com
  16. */
  17. public function order($params = []);
  18. /**
  19. * @param array $params
  20. * @return mixed
  21. * @datetime 2020/7/12 6:46 PM
  22. * @author roach
  23. * @email jhq0113@163.com
  24. */
  25. public function login($params = []);
  26. }
  27. /**
  28. * Class ProductOrder
  29. * @datetime 2020/7/12 6:48 PM
  30. * @author roach
  31. * @email jhq0113@163.com
  32. */
  33. class ProductOrder implements IOrderAndLogin
  34. {
  35. /**
  36. * Order constructor.
  37. * @param array $params
  38. */
  39. public function order($params = [])
  40. {
  41. //各种下单操作
  42. return true;
  43. }
  44. /**
  45. * @param array $params
  46. * @return bool|mixed
  47. * @datetime 2020/7/12 6:48 PM
  48. * @author roach
  49. * @email jhq0113@163.com
  50. */
  51. public function login($params = [])
  52. {
  53. return false;
  54. }
  55. }
  56. /**
  57. * Class WxLogin
  58. * @datetime 2020/7/12 6:50 PM
  59. * @author roach
  60. * @email jhq0113@163.com
  61. */
  62. class WxLogin implements IOrderAndLogin
  63. {
  64. /**
  65. * @param array $params
  66. * @return bool|mixed
  67. * @datetime 2020/7/12 6:49 PM
  68. * @author roach
  69. * @email jhq0113@163.com
  70. */
  71. public function order($params = [])
  72. {
  73. return false;
  74. }
  75. /**
  76. * @param array $params
  77. * @return array|mixed
  78. * @datetime 2020/7/12 6:50 PM
  79. * @author roach
  80. * @email jhq0113@163.com
  81. */
  82. public function login($params = [])
  83. {
  84. // 各种操作
  85. return [
  86. 'userId' => time(),
  87. 'nickname' => uniqid()
  88. ];
  89. }
  90. }
  91. /**
  92. * Class OrderController
  93. * @datetime 2020/7/12 6:55 PM
  94. * @author roach
  95. * @email jhq0113@163.com
  96. */
  97. class OrderController
  98. {
  99. /**
  100. * @param IOrderAndLogin $order
  101. * @return mixed
  102. * @datetime 2020/7/12 6:55 PM
  103. * @author roach
  104. * @email jhq0113@163.com
  105. */
  106. public function orderAction(IOrderAndLogin $order)
  107. {
  108. return $order->order([
  109. 'time' => time(),
  110. ]);
  111. }
  112. }

以上代码我们可以看到,OrderController通过接口IOrderAndLogin依赖ProductOrder,但是IOrderAndLogin接口还有一个OrderController用不到的接口方法login,同时ProductOrder类根本不需要login方法,同样的,WxLogin类也不需要order方法,但是他们各自都必须去实现不需要的方法,这就不符合接口的单一职责原则。

这种优化思路很简单,把IOrderAndLogin接口拆分成IOrderILogin两个接口,ProductOrder类实现IOrder接口,WxLogin类实现ILogin接口,OrderController类依赖IOrder接口即可,由于代码很简单,不在提供实现细节。

3.5 迪米特法则

迪米特法则简单来说就是高内聚、低耦合,一个对象应该对其他对象保持最少的了解。

从耦合度来讲,继承>依赖,所以当我们使用继承的时候,除非是有明确的父子关系,否则不要乱用继承。

下面举一个简单的高内聚、低耦合的例子

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Jiang Haiqiang
  5. * Date: 2020/7/12
  6. * Time: 4:09 PM
  7. */
  8. class RedisCache
  9. {
  10. /**
  11. * @var string
  12. * @datetime 2020/7/12 7:10 PM
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. public $host;
  17. /**
  18. * @var int
  19. * @datetime 2020/7/12 7:10 PM
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public $port;
  24. /**
  25. * @var string
  26. * @datetime 2020/7/12 7:10 PM
  27. * @author roach
  28. * @email jhq0113@163.com
  29. */
  30. public $password;
  31. /**
  32. * @var int
  33. * @datetime 2020/7/12 7:10 PM
  34. * @author roach
  35. * @email jhq0113@163.com
  36. */
  37. public $db;
  38. /**
  39. * @var \Redis
  40. * @datetime 2020/7/12 7:11 PM
  41. * @author roach
  42. * @email jhq0113@163.com
  43. */
  44. private $_instance;
  45. /**
  46. * @return \Redis
  47. * @datetime 2020/7/12 7:13 PM
  48. * @author roach
  49. * @email jhq0113@163.com
  50. */
  51. public function getRedis()
  52. {
  53. if(is_null($this->_instance)) {
  54. $redis = new \Redis();
  55. $redis->connect($this->host, $this->port);
  56. $redis->auth($this->password);
  57. $redis->select($this->db);
  58. $this->_instance = $redis;
  59. }
  60. return $this->_instance;
  61. }
  62. /**
  63. * @param string $key
  64. * @return bool|string
  65. * @datetime 2020/7/12 7:14 PM
  66. * @author roach
  67. * @email jhq0113@163.com
  68. */
  69. public function get($key)
  70. {
  71. return $this->getRedis()->get($key);
  72. }
  73. /**
  74. * @param string $key
  75. * @param string $value
  76. * @param int $timeout
  77. * @return bool
  78. * @datetime 2020/7/12 7:14 PM
  79. * @author roach
  80. * @email jhq0113@163.com
  81. */
  82. public function set($key, $value, $timeout = 60)
  83. {
  84. return $this->getRedis()->set($key, $value, $timeout);
  85. }
  86. }

以上RedisCache封装类,也许大家在自己的项目中肯定看到过类似的代码,大家会觉得这有什么问题吗?

其实从高内聚低耦合的角度来讲,host,port,passworddb属性以及getRedis方法,对于其他类来说根本不需要知道,本着高内聚低耦合的思想,这些的访问权限至少要控制在protected级别,这样从技术角度避免了泄露和被修改的可能性。

3.6 开闭原则

开闭原则简单来说就是对修改关闭,对扩展开放,此原则理解起来稍复杂,我们可以狭义的这样理解。

如果我们的系统软件已经上线运行了,当发生新需求变化时,我们是通过增加代码实现来满足新需求而不是修改原来的代码来实现新需求,当然这是一个狭义的理解。

我们回头看依赖倒置的例子就是一个符合开闭原则的典型案例,当我们增加KafkaLog需求时,我们是通过增加KafkaLog类实现来满足需求,原有的FileLog类和DbLog类没有发生任何更改,这样就是一个典型的对扩展开放,对修改关闭的一个例子。

好了,设计模式的六大原则我们都通过案例学习完了,你都学会了吗?

QQ群:

姜海强的QQ群

公众号:

360tryst公众号