面向对象
不管你是商城、直播或者其他任何类型的项目,都需要面向对象编程。面向对象来源于生活,同时解决的也是生活中的问题,面向对象模拟真实生活中解决问题的方式来解决问题,比如,当我们生病需要就医时,我们会到某个医院或者诊所去看医生,医生就是个类,某个科室的某××医生就是我们
new
出来的对象。生活中类似的例子还有很多,就不再一一列举。
关于面向对象编程,是一个非常能够考察一个开发人员编码功底的技术点。刚走出校园的应届毕业生、做过2年左右的业务开发和具有5年以上底层框架开发经验的三个人对面向对象的理解是完全不同层次的。
我的文章里为啥要讲到面向对象编程呢?因为从我接触过的
PHP
项目,除了使用到的开源框架之外,面向对象运用的确实不是很好。有的同事儿在定义方法时static
关键字不知道该不该加,调用的时候也是,分不清该->
调用还是::
调用。
遇到上述问题,我分析了一下原因,我觉得主要原因有一下几个方面吧:
- a.
PHP
早期版本不支持面向对象,另外PHP
绝大部分函数都是以面向过程调用的方式提供的,如:array_push
,strpos
等 - b.目前来看,
PHP
面试重视基础算法、数据结构等,往往会忽视了对面向对象的考察 - c.企业内部没有制定相应的编码规范,同时也没有学习采纳
PSR
编码规范,或者即使有编码规范也没有严格按照编码规范去执行
1. 封装
封装
就是我们将具有相同属性特征或者行为的事物抽象为一个简单的逻辑单元,对外不完全暴露自己的属性、行为和实现细节,只暴露一些公共的属性或者访问方法,符合高内聚
、低耦合
的设计思想。
在PHP语言中,用
class
关键字封装类,在类中定义自己的属性和方法,下面是一个简单的User对象封装。
<?php
/**
* Class Login
* @datetime 2020/6/28 2:26 下午
* @author roach
* @email jhq0113@163.com
*/
class Login
{
//region 1.1 属性
/**
* @var string
* @datetime 2020/6/28 2:32 下午
* @author roach
* @email jhq0113@163.com
*/
private $_salt = 'de46#@dfsfe';
/**
* @var int
* @datetime 2020/6/28 2:30 下午
* @author roach
* @email jhq0113@163.com
*/
public $lifetime = 3600 * 24 * 15;
/**
* @var int
* @datetime 2020/6/28 2:25 下午
* @author roach
* @email jhq0113@163.com
*/
protected $_id;
//endregion
//region 1.2 方法
/**
* @return int
* @datetime 2020/6/28 2:52 下午
* @author roach
* @email jhq0113@163.com
*/
public function getId()
{
return $this->_id;
}
/**
* @return string
* @datetime 2020/6/28 2:52 下午
* @author roach
* @email jhq0113@163.com
*/
private function _createToken()
{
return md5($this->_salt.$this->_id);
}
/**
* @datetime 2020/6/28 2:54 下午
* @author roach
* @email jhq0113@163.com
*/
protected function _cacheLogin()
{
//cache登录用户信息
//...
}
/**
* @param array $params
* @return bool
* @datetime 2020/6/28 2:51 下午
* @author roach
* @email jhq0113@163.com
*/
public function login($params = [])
{
//获取用户信息
//...
if(is_null($this->_id)) {
return false;
}
return true;
}
//endregion
}
2. 继承
和其他编程语言一样,
PHP
也支持继承且只能单继承,在PHP中实现继承要使用extends
关键字,如下定义一个User
类的子类。
class Phone extends Login
{
}
$login = new Phone();
var_dump($login->lifetime); //正常输出
var_dump($login->_id); // PHP Fatal error: Uncaught Error: Cannot access protected property Phone::$_id
var_dump($login->_salt); // PHP Notice: Undefined property: Phone::$_salt
- final方法
注意:当看到类中方法有
final
关键字修饰时,表示该方法不可以被重写
/**
* Class Order
* @datetime 2020/6/28 11:03 PM
* @author roach
* @email jhq0113@163.com
*/
class Order
{
/**
* @return string
* @datetime 2020/6/28 11:02 PM
* @author roach
* @email jhq0113@163.com
*/
final public function createId()
{
return uniqid();
}
}
/**
* Class PayOrder
* @datetime 2020/6/28 11:02 PM
* @author roach
* @email jhq0113@163.com
*/
class PayOrder extends Order
{
/**
* @return string
* @datetime 2020/6/28 11:02 PM
* @author roach
* @email jhq0113@163.com
*/
public function createId()
{
return uniqid('pay:');
}
}
运行以上例程会报Fatal error
Fatal error: Cannot override final method Order::createId()
3. 类成员访问控制
PHP
语言类的属性和方法也是有访问控制的,具体控制如下表。
关键字 | 类本身 | 子类 | 外部 |
---|---|---|---|
public | √ | √ | √ |
protected | √ | √ | × |
private | √ | × | × |
注意:同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。
class User
{
/**
* @var string
* @datetime 2020/6/28 2:32 下午
* @author roach
* @email jhq0113@163.com
*/
private $_salt = 'de46#@dfsfe';
/**
* @param User $user
* @datetime 2020/6/28 3:26 下午
* @author roach
* @email jhq0113@163.com
*/
public function updateSalt(User $user)
{
$user->_salt = time();
echo $user->_salt.PHP_EOL;
}
}
$user = new User();
$user->updateSalt($user);
以上例程输出:
1593329229
4. 构造函数
和其他语言一样,PHP类也有
构造函数
,不同的是PHP只允许定义一个构造函数,切构造函数名称固定为__construct
<?php
class Sapp
{
/**
* @var string $_appId
* @datetime 2020/6/28 5:14 下午
* @author roach
* @email jhq0113@163.com
*/
protected $_appId;
/**
* @var string $_secretKey
* @datetime 2020/6/28 5:14 下午
* @author roach
* @email jhq0113@163.com
*/
protected $_secretKey;
/**
* Sapp constructor.
* @param $appId
* @param $secretKey
*/
public function __construct($appId, $secretKey)
{
$this->_appId = $appId;
$this->_secretKey = $secretKey;
}
/**
* @return array
* @datetime 2020/6/28 5:17 下午
* @author roach
* @email jhq0113@163.com
*/
public function info()
{
return [
'appId' => $this->_appId,
'secretKey' => $this->_secretKey
];
}
}
$sapp = new Sapp('sadf23324wer', 'sdf23sfsa23rsfdfasd');
var_dump($sapp->info());
以上例程输出:
array(2) {
["appId"]=>
string(12) "sadf23324wer"
["secretKey"]=>
string(19) "sdf23sfsa23rsfdfasd"
}
5. 多态
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的引用,来调用实现派生类中的方法,如下案例:
<?php
/**
* Class Login
* @datetime 2020/6/28 6:58 下午
* @author roach
* @email jhq0113@163.com
*/
class ILogin
{
/**
* @var string
* @datetime 2020/6/28 6:58 下午
* @author roach
* @email jhq0113@163.com
*/
protected $_token;
/**
* @return string
* @datetime 2020/6/28 7:00 下午
* @author roach
* @email jhq0113@163.com
*/
public function getToken()
{
return $this->_token;
}
/**
* @param ILogin $login
* @param array $params
* @return bool
* @datetime 2020/6/28 7:08 下午
* @author roach
* @email jhq0113@163.com
*/
public static function loginResult(ILogin $login, $params = [])
{
return $login->login($params);
}
/**
* @param array $params
* @return bool
* @datetime 2020/6/28 7:08 下午
* @author roach
* @email jhq0113@163.com
*/
public function login($params = [])
{
}
}
/**
* Class Phone
* @datetime 2020/6/28 7:02 下午
* @author roach
* @email jhq0113@163.com
*/
class Phone extends ILogin
{
/**
* @param array $params
* @return bool|void
* @datetime 2020/6/28 7:02 下午
* @author roach
* @email jhq0113@163.com
*/
public function login($params = [])
{
echo '通过手机号验证码登录'.PHP_EOL;
if(!isset($params['phone'], $params['code'])) {
return false;
}
//各种操作
$this->_token = uniqid();
return true;
}
}
/**
* Class Weixin
* @datetime 2020/6/28 7:08 下午
* @author roach
* @email jhq0113@163.com
*/
class Weixin extends ILogin
{
/**
* @var string
* @datetime 2020/6/28 7:03 下午
* @author roach
* @email jhq0113@163.com
*/
protected $_appId;
/**
* @var string
* @datetime 2020/6/28 7:03 下午
* @author roach
* @email jhq0113@163.com
*/
protected $_secretKey;
/**
* Weixin constructor.
* @param array $config
*/
public function __construct($config = [])
{
foreach ($config as $property => $value) {
if(property_exists($this, $property)) {
$this->$property = $value;
}
}
}
/**
* @param array $params
* @return bool
* @datetime 2020/6/28 7:05 下午
* @author roach
* @email jhq0113@163.com
*/
public function login($params = [])
{
echo '通过微信授权登录'.PHP_EOL;
if(!isset($params['code'])) {
return false;
}
//换去accessToken
//拿到用户信息
//....
$this->_token = uniqid();
return true;
}
}
$phone = new Phone();
$weixin = new Weixin([
'_appId' => 'saaf23sdfas',
'_secretKey' => '23sdafa23sdsad',
]);
ILogin::loginResult($phone, ['phone' => '2313131231', 'code' => 'asdfasdf']);
ILogin::loginResult($weixin, ['code' => 'sdfasdf']);
以上例程输出:
通过手机号验证码登录
通过微信授权登录
6. static
- 静态方法和属性
当类属性和方法加上
static
关键字时,属性就变为静态属性,方法则变为静态方法,此时属性和方法可以不经过实例化而通过类直接调用。
<?php
/**
* Created by PhpStorm.
* User: Jiang Haiqiang
* Date: 2020/6/28
* Time: 10:33 PM
*/
/**
* Class Model
* @datetime 2020/6/28 10:33 PM
* @author roach
* @email jhq0113@163.com
*/
class Model
{
/**
* @var string
* @datetime 2020/6/28 10:33 PM
* @author roach
* @email jhq0113@163.com
*/
public static $tableName = 'user';
/**
* @return string
* @datetime 2020/6/28 10:35 PM
* @author roach
* @email jhq0113@163.com
*/
public static function getDb()
{
return 'db';
}
}
//访问静态属性
var_dump(Model::$tableName);
//访问静态方法
var_dump(Model::getDb());
$model = new Model();
//实例化对象访问静态方法
var_dump($model::getDb());
//实例化对象访问静态属性
var_dump($model::$tableName);
以上历程输出:
string(4) "user"
string(2) "db"
string(2) "db"
string(4) "user"
类静态属性和方法的访问控制和类实例属性方法一致,不在过多介绍。
- 静态延迟绑定
静态延时绑定是PHP很重要的一个知识点,
Yii2
在做Model
的选库和选表都使用了静态延时绑定。在了解静态延迟绑定之前先了解一下一下关键字所代表的含义
关键字 | 含义 |
---|---|
self:: | 静态方法定义类本身 |
parent:: | 静态方法定义类的父类 |
static:: | 实际运行时计算的类,也称静态绑定 |
<?php
/**
* Created by PhpStorm.
* User: Jiang Haiqiang
* Date: 2020/6/28
* Time: 10:33 PM
*/
/**
* Class Model
* @datetime 2020/6/28 10:33 PM
* @author roach
* @email jhq0113@163.com
*/
class Model
{
/**
* @var string
* @datetime 2020/6/28 10:33 PM
* @author roach
* @email jhq0113@163.com
*/
public static $tableName = 'user';
/**
* @return string
* @datetime 2020/6/28 10:35 PM
* @author roach
* @email jhq0113@163.com
*/
public static function getDb()
{
return 'db';
}
/**
* @datetime 2020/6/28 10:53 PM
* @author roach
* @email jhq0113@163.com
*/
public static function getTableName()
{
echo 'self调用:'.self::$tableName.PHP_EOL;
//静态绑定
echo 'static调用:'.static::$tableName.PHP_EOL;
}
}
/**
* Class Product
* @datetime 2020/6/28 10:54 PM
* @author roach
* @email jhq0113@163.com
*/
class Product extends Model
{
public static $tableName = 'product';
/**
* @datetime 2020/6/28 10:54 PM
* @author roach
* @email jhq0113@163.com
*/
public static function parentTableName()
{
//父类
echo 'parent调用:'.parent::$tableName.PHP_EOL;
}
}
Product::getTableName();
Product::parentTableName();
以上历程输出:
self调用:user
static调用:product
parent调用:user
7. 抽象类
定义抽象方法和抽象类是通过
abstract
关键字实现的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现,任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。
注意:抽象的类不能被实例化, 继承一个抽象类的时候,子类必须定义父类中的所有抽象方法
<?php
/**
* Created by PhpStorm.
* User: Jiang Haiqiang
* Date: 2020/6/28
* Time: 11:08 PM
*/
/**
* Class Logger
* @datetime 2020/6/28 11:08 PM
* @author roach
* @email jhq0113@163.com
*/
abstract class Logger
{
abstract public function writeLog($level, $msg, $params = []);
}
/**
* Class File
* @datetime 2020/6/28 11:09 PM
* @author roach
* @email jhq0113@163.com
*/
class File extends Logger
{
public $fileName = '/tmp/logs/app.log';
/**
* @param $level
* @param $msg
* @param array $params
* @datetime 2020/6/28 11:11 PM
* @author roach
* @email jhq0113@163.com
*/
public function writeLog($level, $msg, $params = [])
{
//一顿操作
file_put_contents($this->fileName, $msg, FILE_APPEND);
}
}
8. 接口
接口是通过
interface
关键字来定义的,接口中所有
方法只是声明了其调用方式(参数),不能定义其具体的功能实现,接口中定义的所有方法都必须是公有,这是接口的特性
<?php
/**
* Created by PhpStorm.
* User: Jiang Haiqiang
* Date: 2020/6/28
* Time: 11:16 PM
*/
/**
* Class Factory
* @datetime 2020/6/28 11:16 PM
* @author roach
* @email jhq0113@163.com
*/
interface IFactory
{
/**
* @param array $config
* @return mixed
* @datetime 2020/6/28 11:17 PM
* @author roach
* @email jhq0113@163.com
*/
public function createObject($config = []);
}
/**
* Class Factory
* @datetime 2020/6/28 11:28 PM
* @author roach
* @email jhq0113@163.com
*/
class Factory implements IFactory
{
/**
* @param array $config
* @return mixed
* @throws Exception
* @datetime 2020/6/28 11:28 PM
* @author roach
* @email jhq0113@163.com
*/
public function createObject($config = [])
{
if(!isset($config['class'])) {
throw new \Exception('class必传');
}
$className = $config['class'];
unset($config['class']);
if(!class_exists($className)) {
throw new \Exception('类'.$className.'不存在');
}
$object = new $className();
if(!empty($config)) {
foreach ($config as $property => $value) {
$object->$property = $value;
}
}
if(method_exists($object, 'init')) {
$object->init();
}
return $object;
}
}
- 接口和抽象类对比
条目 | 抽象类 | 接口 |
---|---|---|
被实例化 | × | × |
子类必须实现未实现的方法 | √ | √ |
定义属性 | √ | × |
属性和方法可以为protected或private | √ | × |
继承或实现关键字 | extends | implements |
多继承或实现 | × | √ |
9. trait
trait
是为类似PHP
的单继承而准备的一种代码复用机制。trait
为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用method
。trait
和class
组合的语义定义了一种减少复杂性的方式,避免传统多继承和Mixin
类相关典型问题。
<?php
/**
* Trait Event
* @datetime 2020/6/29 8:05 下午
* @author roach
* @email jhq0113@163.com
*/
trait Event
{
/**
* @var array
* @datetime 2020/6/29 8:05 下午
* @author roach
* @email jhq0113@163.com
*/
private $_eventPool = [];
/**
* @param string $name
* @param callable $handler
* @datetime 2020/6/29 8:07 下午
* @author roach
* @email jhq0113@163.com
*/
public function on($name, callable $handler)
{
if(!isset($this->_eventPool[ $name ])) {
$this->_eventPool[ $name ] = [];
}
array_push($this->_eventPool[ $name ], $handler);
}
}
/**
* Class Application
* @datetime 2020/6/29 8:09 下午
* @author roach
* @email jhq0113@163.com
*/
class Application
{
//使用trait
use Event;
/**
* @param string $name
* @datetime 2020/6/29 8:08 下午
* @author roach
* @email jhq0113@163.com
*/
public function trigger($name)
{
if(!isset($this->_eventPool[ $name ])) {
return;
}
foreach ($this->_eventPool[ $name ] as $handler) {
call_user_func($handler);
}
}
}
$app = new Application();
$app->on('start', function (){
echo 'start at '.date('Y-m-d H:i:s').PHP_EOL;
});
$app->on('start', function (){
echo 'start success'.PHP_EOL;
});
$app->trigger('start');
以上例程输出:
start at 2020-06-29 12:11:47
start success
从以上例程我们可以看出,
trait
中的变量和方法可以当做类中的一样使用,甚至private
的属性也可以访问到,的确很爽,但是假如基类、类、trait
和子类中有同名的属性或者方法,是怎么处理的呢?其实是有如下优先级的:子类>类>trait>基类
编写如下代码:
/**
* Class Animal
* @datetime 2020/6/29 8:41 下午
* @author roach
* @email jhq0113@163.com
*/
class Animal
{
/**
* @datetime 2020/6/29 8:40 下午
* @author roach
* @email jhq0113@163.com
*/
public function run()
{
echo 'parent run'.PHP_EOL;
}
}
/**
* Trait TRun
* @datetime 2020/6/29 8:41 下午
* @author roach
* @email jhq0113@163.com
*/
trait TRun
{
public function run()
{
echo 'trait run'.PHP_EOL;
}
}
/**
* Class Rabbit
* @datetime 2020/6/29 8:42 下午
* @author roach
* @email jhq0113@163.com
*/
class Rabbit extends Animal
{
use TRun;
/**
* @datetime 2020/6/29 8:42 下午
* @author roach
* @email jhq0113@163.com
*/
public function run()
{
echo 'Rabbit run'.PHP_EOL;
}
}
/**
* Class RedRabbit
* @datetime 2020/6/29 8:43 下午
* @author roach
* @email jhq0113@163.com
*/
class RedRabbit extends Rabbit
{
/**
* @datetime 2020/6/29 8:43 下午
* @author roach
* @email jhq0113@163.com
*/
public function run()
{
echo 'red rabbit run'.PHP_EOL;
}
}
//子类优先级最高
$redrabbit = new RedRabbit();
$redrabbit->run();
//类第二
$rabbit = new Rabbit();
$rabbit->run();
以上例程输出:
red rabbit run
Rabbit run
如果将
Rabbit
类中的run
方法注释掉,有以下输出:
red rabbit run
trait run
通过以上输出结果即可验证优先级关系。
好了,看到这里已经把面向对象的基础简单的过了一遍,当然关于面向对象还没有学完,本系列文章会在设计模式的章节中继续介绍面向对象编程的一些思考和经验,希望能给大家带来帮助。