面向对象编程和面向对象设计的五个基本原则:
- 单一职责原则(Single Responsibility Principle)---使用依赖注入,各类执行各自功能,不因任何一个类变化产生变动
- 开放封闭原则(Open Closed Principle) ---对扩展是开放的,对修改是封闭的
- 里氏替换原则(Liskov Substitution Principle) ---灵活切换/替换类,而不修改更多的代码
- 接口隔离原则(Interface Segregation Principle)---与接口产生依赖,而不与具体实现接口产生依赖
- 依赖反转原则(Dependency Inversion Principle) ---高层次不依赖低层次代码,低层次代码依赖高层次代码
在多人开发项目的时候,我是建议大家用同一种编程开发工具;程序员本身要遵从这五个原则,对之后的维护是非常方便的,虽然开发的时候麻烦了点,现在我详细用实例来分别解释五个原则。
单一职责原则
单一职责规定一个类有且仅有一个理由使其改变。换句话说各类执行各自功能,不因任何一个类变化产生变动
例如我们现在有个订单要退款,并且验证订单是否已退款
class orderProcessor
{
public function doRefund(Order $order)
{
$result = $this->checkOrderHasRefund($order->id);
if ($result)
return false;
//更新订单状态
$this->db->update('order', ['id' => $id, 'status' => 0], ['status' => 1]);
//调用退款请求
}
public function checkOrderHasRefund($id)
{
return $this->db->select('*')->from('order')->where(['status' => 1, 'id' => $id])->get()->row_array();
}
}
这个类中使用到了数据库操作,这就是不符合单一职责原则,如果数据发生改变,如关系数据库换成非关系数据库呢,所以我们要把数据库操作抽离出来,如下:
class OrderModel
{
public function checkOrderHasRefund($id)
{
return $this->db->select('*')->from('order')->where(['status' => 1, 'id' => $id])->get()->row_array();
}
public function updateOrderStatus($id, $status) {
return $this->db->update('order', ['id' => $id, 'status' => 0], ['status' => $status]);
}
}
class OrderProcessor
{
public $orderModel;
public function __construct(OrderModel $orderModel)
{
$this->orderModel = $orderModel;
}
public function doRefund(Order $order)
{
$result = $this->orderModel->checkOrderHasRefund($order->id);
if ($result)
return false;
//更新订单状态
$result = $this->orderModel->updateOrderStatus($order->id, 9);
if (!$result) {
return false;
}
//调用退款请求
}
}
class OrderProcessor
{
public $orderModel;
public function __construct(OrderModel $orderModel)
{
$this->orderModel = $orderModel;
}
public function doRefund(Order $order)
{
$result = $this->orderModel->checkOrderHasRefund($order->id);
if ($result)
return false;
//更新订单状态
$result = $this->orderModel->updateOrderStatus($order->id, 9);
if (!$result) {
return false;
}
//调用退款请求
}
}
这样使用依赖注入,各类执行各自功能,不因任何一个类变化产生变动。
开放封闭原则
一个项目已经成型,后期的新增修改是十分令人头疼的事,在你修改代码的时候都有可能引入新bug,或者破坏原有的功能。这时开放封闭原则的设计,理想的情况下我们就可以避免这种情况的发生,快速修改现有的代码。用上面的代码举例子
$result = $this->orderModel->checkOrderHasRefund($id);
if ($result)
return false;
这段代码是判断订单是否付款,如果后续出现新增的验证功能呢,比如三天前订单不可退款、哪种支付订单可退款等等,我们怎么可以做到再步修改原有的代码上进行扩展呢。我们可以新建一个接口
interface OrderValidatorInterface
{
public function validate(Order $order);
}
我们可以把所有的验证订单都继承这个接口,RefundOrderValidate验证订单是否付款,OrderPayValidate验证订单支付方式
class RefundOrderValidate implements OrderValidatorInterface
{
public function __construct(OrderModel $orderModel)
{
$this->orderModel = $orderModel;
}
public function validate(Order $order)
{
return $this->orderModel->checkOrderHasRefund($order->id);
}
}
class OrderPayValidate implements OrderValidatorInterface
{
public function __construct(OrderModel $orderModel)
{
$this->orderModel = $orderModel;
}
public function validate(Order $order)
{
$data = $this->orderModel->payMethod($order->id);
if ($data['pat_type'] == $order->pay_type)
return true;
else
return false;
}
}
class OrderPayValidate implements OrderValidatorInterface
{
public function __construct(OrderModel $orderModel)
{
$this->orderModel = $orderModel;
}
public function validate(Order $order)
{
$data = $this->orderModel->payMethod($order->id);
if ($data['pat_type'] == $order->pay_type)
return true;
else
return false;
}
}
class OrderModel
{
public function checkOrderHasRefund($id)
{
return $this->db->select('*')->from('order')->where(['status' => 1, 'id' => $id])->get()->row_array();
}
public function updateOrderStatus($id, $status)
{
return $this->db->update('order', ['id' => $id, 'status' => 0], ['status' => $status]);
}
public function payMethod($id){
return $this->db->select('pay_type')->from('order')->where(['status' => 1, 'id' => $id])->get()->row_array();
}
}
现在有两个类来实现OrderValidatorInterface接口,那我们就可以在OrderProcessor中使用他们了
class OrderProcessor
{
public function __construct(OrderModel $orderModel, array $validators = [])
{
$this->orderModel = $orderModel;
$this->validators = $validators;
}
public function doRefund(Order $order)
{
foreach ($this->validators as $validator){
$res = $validator->validate();
if (!$res)
return false;
}
//更新订单状态
$result = $this->orderModel->updateOrderStatus($order->id, 9);
if (!$result) {
return false;
}
//调用退款请求
}
}
之后如果再有其他的规则验证,我们只有新增继承OrderValidatorInterface的类,就可以了而步修改原有的代码。
对开发封闭原则可以理解为:对扩展是开放的,对修改是封闭的。
里氏替换原则
里氏替换原则规定对象可以被其子类的实例所替换,并且不会影响导现有程序的正确性。
这里我就讲下这个里氏替换原则的意思,如果一个类使用到了某个接口来实现,那么一定可以通过该接口的其它实现来替换它,而不做出任何修改。这句话有点绕不好理解,看一下代码来帮助你理解
class OrderProcessor
{
public function __construct(OrderModel $orderModel, LogInterface $repository, array $validators = [])
{
$this->orderModel = $orderModel;
$this->validators = $validators;
$this->repository = $repository;
}
public function doRefund(Order $order)
{
//记录日志
$this->repository->log('order_log', '订单退款操作记录');
foreach ($this->validators as $validator) {
$res = $validator->validate();
if (!$res)
return false;
}
//更新订单状态
$result = $this->orderModel->updateOrderStatus($order->id, 9);
if (!$result) {
return false;
}
//调用退款请求
}
}
interface LogInterface
{
public function doLog($file, $text);
}
class FileLog implements LogInterface
{
public function doLog($file, $text)
{
file_put_contents($file, $text);
}
}
class LogModel implements LogInterface
{
protected $connect;
public function connect(DatabaseConnector $connector)
{
$this->connect = $connector;
}
public function doLog($file, $text)
{
$this->connect->insert('log', ['file' => $file, 'text' => $text]);
}
}
我们在orderProcessor中新增日志,和注入接口$repository,这个接口我们可以是LogModel或者FileLog,可以随意可替换的而不修改orderProcessor中的代码。
接口隔离原则
接口隔离原则规定不强制接口的实现不依赖它不是用的方法。意思是说在一个inerface中定义了几个function,依赖这个inteface的子类不应该强制必须实现interface中的function,而是应该用更细小的划分创建inerface。例如PHP自带的SessionHandlerInterface.php
interface SessionHandlerInterface {
public function close();
public function destroy($sessionId);
public function gc($maxLifetime);
public function open($savePath, $name);
public function read($sesssionId);
public function write($sessionId, $sessionData);
}
这个类是定义了要实现会话处理器必须要实现的几个方法,默认情况下都是使用文件缓存来实现的,如果替换成Redis呢,在Redis中有自带的过期缓存回收,不会使用到gc。从这个方面上我们可以把SessionHandlerInterface 颗粒化
interface GarbageCollectorInterface
{
public function gc($maxLifetime);
}
有了这个我们就不用去依赖整个会话处理器。
依赖反转原则
依赖反转其实是很好理解的,是对代码的分层,高层次和低层次;程序设计的时候高层次代码不依赖低层次代码。
比如我们现在要做一个根据id查询用户信息的接口
interface UserInterface
{
public function findUserById($id);
}
class RedisUserProvider implements UserInterface
{
protected $redis;
public function __construct(RedisConnection $redis)
{
$this->redis = $redis;
}
public function findUserById($id) {
return $this->redis->get('user_' . $id);
}
}
class User
{
public function __construct(UserInterface $users)
{
$this->users = $users;
}
public function userInfo ($id) {
return $this->users->findUserById($id);
}
}
这段代码你有没有发现 User 这个类中注入的依赖不是 RedisUserProvider 而是 UserInterface这个就是低层依赖高层,这样我们从 Redis 中获取换成MYSQL中获取用户信息,就不会出现问题。
看完上面内容,是不是有中感觉五个规则互相存在联系,那就是了,程序设计如果有一个不符合规则,必定会引起其他规则的不符合,例如最后一个依赖反转规则不是低层次代码依赖高层次,就会违背里氏替换原来,就无法用MYSQL替换Redis。
最后以上代码只是帮组理解。