作者简介:

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

视频课程

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

Github

个人主页
swoole-boot
roach
roach-orm

QQ群:

姜海强的QQ群

公众号:

360tryst公众号

面向对象

不管你是商城、直播或者其他任何类型的项目,都需要面向对象编程。面向对象来源于生活,同时解决的也是生活中的问题,面向对象模拟真实生活中解决问题的方式来解决问题,比如,当我们生病需要就医时,我们会到某个医院或者诊所去看医生,医生就是个类,某个科室的某××医生就是我们new出来的对象。生活中类似的例子还有很多,就不再一一列举。

关于面向对象编程,是一个非常能够考察一个开发人员编码功底的技术点。刚走出校园的应届毕业生、做过2年左右的业务开发和具有5年以上底层框架开发经验的三个人对面向对象的理解是完全不同层次的。

我的文章里为啥要讲到面向对象编程呢?因为从我接触过的PHP项目,除了使用到的开源框架之外,面向对象运用的确实不是很好。有的同事儿在定义方法时static关键字不知道该不该加,调用的时候也是,分不清该->调用还是::调用。

遇到上述问题,我分析了一下原因,我觉得主要原因有一下几个方面吧:

  • a.PHP早期版本不支持面向对象,另外PHP绝大部分函数都是以面向过程调用的方式提供的,如:array_push, strpos
  • b.目前来看,PHP面试重视基础算法、数据结构等,往往会忽视了对面向对象的考察
  • c.企业内部没有制定相应的编码规范,同时也没有学习采纳PSR编码规范,或者即使有编码规范也没有严格按照编码规范去执行

1. 封装

封装就是我们将具有相同属性特征或者行为的事物抽象为一个简单的逻辑单元,对外不完全暴露自己的属性、行为和实现细节,只暴露一些公共的属性或者访问方法,符合高内聚低耦合的设计思想。

在PHP语言中,用class关键字封装类,在类中定义自己的属性和方法,下面是一个简单的User对象封装。

  1. <?php
  2. /**
  3. * Class Login
  4. * @datetime 2020/6/28 2:26 下午
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. class Login
  9. {
  10. //region 1.1 属性
  11. /**
  12. * @var string
  13. * @datetime 2020/6/28 2:32 下午
  14. * @author roach
  15. * @email jhq0113@163.com
  16. */
  17. private $_salt = 'de46#@dfsfe';
  18. /**
  19. * @var int
  20. * @datetime 2020/6/28 2:30 下午
  21. * @author roach
  22. * @email jhq0113@163.com
  23. */
  24. public $lifetime = 3600 * 24 * 15;
  25. /**
  26. * @var int
  27. * @datetime 2020/6/28 2:25 下午
  28. * @author roach
  29. * @email jhq0113@163.com
  30. */
  31. protected $_id;
  32. //endregion
  33. //region 1.2 方法
  34. /**
  35. * @return int
  36. * @datetime 2020/6/28 2:52 下午
  37. * @author roach
  38. * @email jhq0113@163.com
  39. */
  40. public function getId()
  41. {
  42. return $this->_id;
  43. }
  44. /**
  45. * @return string
  46. * @datetime 2020/6/28 2:52 下午
  47. * @author roach
  48. * @email jhq0113@163.com
  49. */
  50. private function _createToken()
  51. {
  52. return md5($this->_salt.$this->_id);
  53. }
  54. /**
  55. * @datetime 2020/6/28 2:54 下午
  56. * @author roach
  57. * @email jhq0113@163.com
  58. */
  59. protected function _cacheLogin()
  60. {
  61. //cache登录用户信息
  62. //...
  63. }
  64. /**
  65. * @param array $params
  66. * @return bool
  67. * @datetime 2020/6/28 2:51 下午
  68. * @author roach
  69. * @email jhq0113@163.com
  70. */
  71. public function login($params = [])
  72. {
  73. //获取用户信息
  74. //...
  75. if(is_null($this->_id)) {
  76. return false;
  77. }
  78. return true;
  79. }
  80. //endregion
  81. }

2. 继承

和其他编程语言一样,PHP也支持继承且只能单继承,在PHP中实现继承要使用extends关键字,如下定义一个User类的子类。

  1. class Phone extends Login
  2. {
  3. }
  4. $login = new Phone();
  5. var_dump($login->lifetime); //正常输出
  6. var_dump($login->_id); // PHP Fatal error: Uncaught Error: Cannot access protected property Phone::$_id
  7. var_dump($login->_salt); // PHP Notice: Undefined property: Phone::$_salt
  • final方法

注意:当看到类中方法有final关键字修饰时,表示该方法不可以被重写

  1. /**
  2. * Class Order
  3. * @datetime 2020/6/28 11:03 PM
  4. * @author roach
  5. * @email jhq0113@163.com
  6. */
  7. class Order
  8. {
  9. /**
  10. * @return string
  11. * @datetime 2020/6/28 11:02 PM
  12. * @author roach
  13. * @email jhq0113@163.com
  14. */
  15. final public function createId()
  16. {
  17. return uniqid();
  18. }
  19. }
  20. /**
  21. * Class PayOrder
  22. * @datetime 2020/6/28 11:02 PM
  23. * @author roach
  24. * @email jhq0113@163.com
  25. */
  26. class PayOrder extends Order
  27. {
  28. /**
  29. * @return string
  30. * @datetime 2020/6/28 11:02 PM
  31. * @author roach
  32. * @email jhq0113@163.com
  33. */
  34. public function createId()
  35. {
  36. return uniqid('pay:');
  37. }
  38. }

运行以上例程会报Fatal error

  1. Fatal error: Cannot override final method Order::createId()

3. 类成员访问控制

PHP语言类的属性和方法也是有访问控制的,具体控制如下表。

关键字 类本身 子类 外部
public
protected ×
private × ×

注意:同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。

  1. class User
  2. {
  3. /**
  4. * @var string
  5. * @datetime 2020/6/28 2:32 下午
  6. * @author roach
  7. * @email jhq0113@163.com
  8. */
  9. private $_salt = 'de46#@dfsfe';
  10. /**
  11. * @param User $user
  12. * @datetime 2020/6/28 3:26 下午
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. public function updateSalt(User $user)
  17. {
  18. $user->_salt = time();
  19. echo $user->_salt.PHP_EOL;
  20. }
  21. }
  22. $user = new User();
  23. $user->updateSalt($user);

以上例程输出:

  1. 1593329229

4. 构造函数

和其他语言一样,PHP类也有构造函数,不同的是PHP只允许定义一个构造函数,切构造函数名称固定为__construct

  1. <?php
  2. class Sapp
  3. {
  4. /**
  5. * @var string $_appId
  6. * @datetime 2020/6/28 5:14 下午
  7. * @author roach
  8. * @email jhq0113@163.com
  9. */
  10. protected $_appId;
  11. /**
  12. * @var string $_secretKey
  13. * @datetime 2020/6/28 5:14 下午
  14. * @author roach
  15. * @email jhq0113@163.com
  16. */
  17. protected $_secretKey;
  18. /**
  19. * Sapp constructor.
  20. * @param $appId
  21. * @param $secretKey
  22. */
  23. public function __construct($appId, $secretKey)
  24. {
  25. $this->_appId = $appId;
  26. $this->_secretKey = $secretKey;
  27. }
  28. /**
  29. * @return array
  30. * @datetime 2020/6/28 5:17 下午
  31. * @author roach
  32. * @email jhq0113@163.com
  33. */
  34. public function info()
  35. {
  36. return [
  37. 'appId' => $this->_appId,
  38. 'secretKey' => $this->_secretKey
  39. ];
  40. }
  41. }
  42. $sapp = new Sapp('sadf23324wer', 'sdf23sfsa23rsfdfasd');
  43. var_dump($sapp->info());

以上例程输出:

  1. array(2) {
  2. ["appId"]=>
  3. string(12) "sadf23324wer"
  4. ["secretKey"]=>
  5. string(19) "sdf23sfsa23rsfdfasd"
  6. }

5. 多态

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的引用,来调用实现派生类中的方法,如下案例:

  1. <?php
  2. /**
  3. * Class Login
  4. * @datetime 2020/6/28 6:58 下午
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. class ILogin
  9. {
  10. /**
  11. * @var string
  12. * @datetime 2020/6/28 6:58 下午
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. protected $_token;
  17. /**
  18. * @return string
  19. * @datetime 2020/6/28 7:00 下午
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public function getToken()
  24. {
  25. return $this->_token;
  26. }
  27. /**
  28. * @param ILogin $login
  29. * @param array $params
  30. * @return bool
  31. * @datetime 2020/6/28 7:08 下午
  32. * @author roach
  33. * @email jhq0113@163.com
  34. */
  35. public static function loginResult(ILogin $login, $params = [])
  36. {
  37. return $login->login($params);
  38. }
  39. /**
  40. * @param array $params
  41. * @return bool
  42. * @datetime 2020/6/28 7:08 下午
  43. * @author roach
  44. * @email jhq0113@163.com
  45. */
  46. public function login($params = [])
  47. {
  48. }
  49. }
  50. /**
  51. * Class Phone
  52. * @datetime 2020/6/28 7:02 下午
  53. * @author roach
  54. * @email jhq0113@163.com
  55. */
  56. class Phone extends ILogin
  57. {
  58. /**
  59. * @param array $params
  60. * @return bool|void
  61. * @datetime 2020/6/28 7:02 下午
  62. * @author roach
  63. * @email jhq0113@163.com
  64. */
  65. public function login($params = [])
  66. {
  67. echo '通过手机号验证码登录'.PHP_EOL;
  68. if(!isset($params['phone'], $params['code'])) {
  69. return false;
  70. }
  71. //各种操作
  72. $this->_token = uniqid();
  73. return true;
  74. }
  75. }
  76. /**
  77. * Class Weixin
  78. * @datetime 2020/6/28 7:08 下午
  79. * @author roach
  80. * @email jhq0113@163.com
  81. */
  82. class Weixin extends ILogin
  83. {
  84. /**
  85. * @var string
  86. * @datetime 2020/6/28 7:03 下午
  87. * @author roach
  88. * @email jhq0113@163.com
  89. */
  90. protected $_appId;
  91. /**
  92. * @var string
  93. * @datetime 2020/6/28 7:03 下午
  94. * @author roach
  95. * @email jhq0113@163.com
  96. */
  97. protected $_secretKey;
  98. /**
  99. * Weixin constructor.
  100. * @param array $config
  101. */
  102. public function __construct($config = [])
  103. {
  104. foreach ($config as $property => $value) {
  105. if(property_exists($this, $property)) {
  106. $this->$property = $value;
  107. }
  108. }
  109. }
  110. /**
  111. * @param array $params
  112. * @return bool
  113. * @datetime 2020/6/28 7:05 下午
  114. * @author roach
  115. * @email jhq0113@163.com
  116. */
  117. public function login($params = [])
  118. {
  119. echo '通过微信授权登录'.PHP_EOL;
  120. if(!isset($params['code'])) {
  121. return false;
  122. }
  123. //换去accessToken
  124. //拿到用户信息
  125. //....
  126. $this->_token = uniqid();
  127. return true;
  128. }
  129. }
  130. $phone = new Phone();
  131. $weixin = new Weixin([
  132. '_appId' => 'saaf23sdfas',
  133. '_secretKey' => '23sdafa23sdsad',
  134. ]);
  135. ILogin::loginResult($phone, ['phone' => '2313131231', 'code' => 'asdfasdf']);
  136. ILogin::loginResult($weixin, ['code' => 'sdfasdf']);

以上例程输出:

  1. 通过手机号验证码登录
  2. 通过微信授权登录

6. static

  • 静态方法和属性

当类属性和方法加上static关键字时,属性就变为静态属性,方法则变为静态方法,此时属性和方法可以不经过实例化而通过类直接调用。

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Jiang Haiqiang
  5. * Date: 2020/6/28
  6. * Time: 10:33 PM
  7. */
  8. /**
  9. * Class Model
  10. * @datetime 2020/6/28 10:33 PM
  11. * @author roach
  12. * @email jhq0113@163.com
  13. */
  14. class Model
  15. {
  16. /**
  17. * @var string
  18. * @datetime 2020/6/28 10:33 PM
  19. * @author roach
  20. * @email jhq0113@163.com
  21. */
  22. public static $tableName = 'user';
  23. /**
  24. * @return string
  25. * @datetime 2020/6/28 10:35 PM
  26. * @author roach
  27. * @email jhq0113@163.com
  28. */
  29. public static function getDb()
  30. {
  31. return 'db';
  32. }
  33. }
  34. //访问静态属性
  35. var_dump(Model::$tableName);
  36. //访问静态方法
  37. var_dump(Model::getDb());
  38. $model = new Model();
  39. //实例化对象访问静态方法
  40. var_dump($model::getDb());
  41. //实例化对象访问静态属性
  42. var_dump($model::$tableName);

以上历程输出:

  1. string(4) "user"
  2. string(2) "db"
  3. string(2) "db"
  4. string(4) "user"

类静态属性和方法的访问控制和类实例属性方法一致,不在过多介绍。

  • 静态延迟绑定

静态延时绑定是PHP很重要的一个知识点,Yii2在做Model的选库和选表都使用了静态延时绑定。在了解静态延迟绑定之前先了解一下一下关键字所代表的含义

关键字 含义
self:: 静态方法定义类本身
parent:: 静态方法定义类的父类
static:: 实际运行时计算的类,也称静态绑定
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Jiang Haiqiang
  5. * Date: 2020/6/28
  6. * Time: 10:33 PM
  7. */
  8. /**
  9. * Class Model
  10. * @datetime 2020/6/28 10:33 PM
  11. * @author roach
  12. * @email jhq0113@163.com
  13. */
  14. class Model
  15. {
  16. /**
  17. * @var string
  18. * @datetime 2020/6/28 10:33 PM
  19. * @author roach
  20. * @email jhq0113@163.com
  21. */
  22. public static $tableName = 'user';
  23. /**
  24. * @return string
  25. * @datetime 2020/6/28 10:35 PM
  26. * @author roach
  27. * @email jhq0113@163.com
  28. */
  29. public static function getDb()
  30. {
  31. return 'db';
  32. }
  33. /**
  34. * @datetime 2020/6/28 10:53 PM
  35. * @author roach
  36. * @email jhq0113@163.com
  37. */
  38. public static function getTableName()
  39. {
  40. echo 'self调用:'.self::$tableName.PHP_EOL;
  41. //静态绑定
  42. echo 'static调用:'.static::$tableName.PHP_EOL;
  43. }
  44. }
  45. /**
  46. * Class Product
  47. * @datetime 2020/6/28 10:54 PM
  48. * @author roach
  49. * @email jhq0113@163.com
  50. */
  51. class Product extends Model
  52. {
  53. public static $tableName = 'product';
  54. /**
  55. * @datetime 2020/6/28 10:54 PM
  56. * @author roach
  57. * @email jhq0113@163.com
  58. */
  59. public static function parentTableName()
  60. {
  61. //父类
  62. echo 'parent调用:'.parent::$tableName.PHP_EOL;
  63. }
  64. }
  65. Product::getTableName();
  66. Product::parentTableName();

以上历程输出:

  1. self调用:user
  2. static调用:product
  3. parent调用:user

7. 抽象类

定义抽象方法和抽象类是通过abstract关键字实现的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现,任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。

注意:抽象的类不能被实例化, 继承一个抽象类的时候,子类必须定义父类中的所有抽象方法

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Jiang Haiqiang
  5. * Date: 2020/6/28
  6. * Time: 11:08 PM
  7. */
  8. /**
  9. * Class Logger
  10. * @datetime 2020/6/28 11:08 PM
  11. * @author roach
  12. * @email jhq0113@163.com
  13. */
  14. abstract class Logger
  15. {
  16. abstract public function writeLog($level, $msg, $params = []);
  17. }
  18. /**
  19. * Class File
  20. * @datetime 2020/6/28 11:09 PM
  21. * @author roach
  22. * @email jhq0113@163.com
  23. */
  24. class File extends Logger
  25. {
  26. public $fileName = '/tmp/logs/app.log';
  27. /**
  28. * @param $level
  29. * @param $msg
  30. * @param array $params
  31. * @datetime 2020/6/28 11:11 PM
  32. * @author roach
  33. * @email jhq0113@163.com
  34. */
  35. public function writeLog($level, $msg, $params = [])
  36. {
  37. //一顿操作
  38. file_put_contents($this->fileName, $msg, FILE_APPEND);
  39. }
  40. }

8. 接口

接口是通过interface关键字来定义的,接口中所有方法只是声明了其调用方式(参数),不能定义其具体的功能实现,接口中定义的所有方法都必须是公有,这是接口的特性

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Jiang Haiqiang
  5. * Date: 2020/6/28
  6. * Time: 11:16 PM
  7. */
  8. /**
  9. * Class Factory
  10. * @datetime 2020/6/28 11:16 PM
  11. * @author roach
  12. * @email jhq0113@163.com
  13. */
  14. interface IFactory
  15. {
  16. /**
  17. * @param array $config
  18. * @return mixed
  19. * @datetime 2020/6/28 11:17 PM
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public function createObject($config = []);
  24. }
  25. /**
  26. * Class Factory
  27. * @datetime 2020/6/28 11:28 PM
  28. * @author roach
  29. * @email jhq0113@163.com
  30. */
  31. class Factory implements IFactory
  32. {
  33. /**
  34. * @param array $config
  35. * @return mixed
  36. * @throws Exception
  37. * @datetime 2020/6/28 11:28 PM
  38. * @author roach
  39. * @email jhq0113@163.com
  40. */
  41. public function createObject($config = [])
  42. {
  43. if(!isset($config['class'])) {
  44. throw new \Exception('class必传');
  45. }
  46. $className = $config['class'];
  47. unset($config['class']);
  48. if(!class_exists($className)) {
  49. throw new \Exception('类'.$className.'不存在');
  50. }
  51. $object = new $className();
  52. if(!empty($config)) {
  53. foreach ($config as $property => $value) {
  54. $object->$property = $value;
  55. }
  56. }
  57. if(method_exists($object, 'init')) {
  58. $object->init();
  59. }
  60. return $object;
  61. }
  62. }
  • 接口和抽象类对比
条目 抽象类 接口
被实例化 × ×
子类必须实现未实现的方法
定义属性 ×
属性和方法可以为protected或private ×
继承或实现关键字 extends implements
多继承或实现 ×

9. trait

trait是为类似PHP的单继承而准备的一种代码复用机制。trait为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用methodtraitclass组合的语义定义了一种减少复杂性的方式,避免传统多继承和Mixin类相关典型问题。

  1. <?php
  2. /**
  3. * Trait Event
  4. * @datetime 2020/6/29 8:05 下午
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. trait Event
  9. {
  10. /**
  11. * @var array
  12. * @datetime 2020/6/29 8:05 下午
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. private $_eventPool = [];
  17. /**
  18. * @param string $name
  19. * @param callable $handler
  20. * @datetime 2020/6/29 8:07 下午
  21. * @author roach
  22. * @email jhq0113@163.com
  23. */
  24. public function on($name, callable $handler)
  25. {
  26. if(!isset($this->_eventPool[ $name ])) {
  27. $this->_eventPool[ $name ] = [];
  28. }
  29. array_push($this->_eventPool[ $name ], $handler);
  30. }
  31. }
  32. /**
  33. * Class Application
  34. * @datetime 2020/6/29 8:09 下午
  35. * @author roach
  36. * @email jhq0113@163.com
  37. */
  38. class Application
  39. {
  40. //使用trait
  41. use Event;
  42. /**
  43. * @param string $name
  44. * @datetime 2020/6/29 8:08 下午
  45. * @author roach
  46. * @email jhq0113@163.com
  47. */
  48. public function trigger($name)
  49. {
  50. if(!isset($this->_eventPool[ $name ])) {
  51. return;
  52. }
  53. foreach ($this->_eventPool[ $name ] as $handler) {
  54. call_user_func($handler);
  55. }
  56. }
  57. }
  58. $app = new Application();
  59. $app->on('start', function (){
  60. echo 'start at '.date('Y-m-d H:i:s').PHP_EOL;
  61. });
  62. $app->on('start', function (){
  63. echo 'start success'.PHP_EOL;
  64. });
  65. $app->trigger('start');

以上例程输出:

  1. start at 2020-06-29 12:11:47
  2. start success

从以上例程我们可以看出,trait中的变量和方法可以当做类中的一样使用,甚至private的属性也可以访问到,的确很爽,但是假如基类、类、trait和子类中有同名的属性或者方法,是怎么处理的呢?其实是有如下优先级的:子类>类>trait>基类

编写如下代码:

  1. /**
  2. * Class Animal
  3. * @datetime 2020/6/29 8:41 下午
  4. * @author roach
  5. * @email jhq0113@163.com
  6. */
  7. class Animal
  8. {
  9. /**
  10. * @datetime 2020/6/29 8:40 下午
  11. * @author roach
  12. * @email jhq0113@163.com
  13. */
  14. public function run()
  15. {
  16. echo 'parent run'.PHP_EOL;
  17. }
  18. }
  19. /**
  20. * Trait TRun
  21. * @datetime 2020/6/29 8:41 下午
  22. * @author roach
  23. * @email jhq0113@163.com
  24. */
  25. trait TRun
  26. {
  27. public function run()
  28. {
  29. echo 'trait run'.PHP_EOL;
  30. }
  31. }
  32. /**
  33. * Class Rabbit
  34. * @datetime 2020/6/29 8:42 下午
  35. * @author roach
  36. * @email jhq0113@163.com
  37. */
  38. class Rabbit extends Animal
  39. {
  40. use TRun;
  41. /**
  42. * @datetime 2020/6/29 8:42 下午
  43. * @author roach
  44. * @email jhq0113@163.com
  45. */
  46. public function run()
  47. {
  48. echo 'Rabbit run'.PHP_EOL;
  49. }
  50. }
  51. /**
  52. * Class RedRabbit
  53. * @datetime 2020/6/29 8:43 下午
  54. * @author roach
  55. * @email jhq0113@163.com
  56. */
  57. class RedRabbit extends Rabbit
  58. {
  59. /**
  60. * @datetime 2020/6/29 8:43 下午
  61. * @author roach
  62. * @email jhq0113@163.com
  63. */
  64. public function run()
  65. {
  66. echo 'red rabbit run'.PHP_EOL;
  67. }
  68. }
  69. //子类优先级最高
  70. $redrabbit = new RedRabbit();
  71. $redrabbit->run();
  72. //类第二
  73. $rabbit = new Rabbit();
  74. $rabbit->run();

以上例程输出:

  1. red rabbit run
  2. Rabbit run

如果将Rabbit类中的run方法注释掉,有以下输出:

  1. red rabbit run
  2. trait run

通过以上输出结果即可验证优先级关系。

好了,看到这里已经把面向对象的基础简单的过了一遍,当然关于面向对象还没有学完,本系列文章会在设计模式的章节中继续介绍面向对象编程的一些思考和经验,希望能给大家带来帮助。

QQ群:

姜海强的QQ群

公众号:

360tryst公众号