摘要:主要是参考列旭松、陈文著的《PHP核心技术与最佳实践》的2.1.3节。
1.1 简介
面向对象设计的五大原则分别是单一指责原则(SRP)、接口隔离原则(ISP)、开放-封闭原则(OCP)、替换原则(LSP)、依赖倒置原则(DIP),这五大原则也是23种设计模式的基础。
而开放-封闭(Open-Close Principle,OCP)原则的基本思想是:
- Open(Open for extension):模块的行为必须是开放的、支持扩展的,而不是僵化的
- Closed(Closed for modification):在对模块的功能进行扩展时,不应该影响或大规模影响已有的程序模块
换句话说,也就是要求开发人员在不修改系统中现有功能代码的前提下,实现对应用系统的软件功能的扩展。用一句话概述就是:一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。
开放封闭原则主要是体现在两个方面:
- 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
- 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
1.2 如何使用
这一节主要是参考百度百科:
实现开放-封闭的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以对修改就是封闭的。而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。
1. 在设计方面充分应用抽象和封装的思想
2. 在系统功能编程实现方面应用面向接口的编程
- 一方面也就是要在软件系统中找出各种可能的可变因素,并将之封装起来
- 另一反面,一种可变性因素不应当散落在多个不同代码模块中,而应当被封装到一个对象中
- 当需求发生变化时,可以提供该接口新的实现类,以求适应变化
- 面向接口编程要求功能类实现接口,对象声明为接口类型。在设计模式中,装饰模式(Decorate)比较明显的使用到了OCP
对于违反这一原则的类,必须通过重构来进行改善。常用于实现的设计模式主要有模板方法模式(Template Method)和策略模式(Strategy)。而封装变化,是实现这一原则的重要手段,将经常变化的状态封装为一个类。
以银行业务员为例,没有实现OCP设计的:
public class BankProcess
{
public void Deposite(){} //存款
public void Withdraw(){} //取款
public void Transfer(){} //转账
}
public class BankStaff
{
private BankProcess bankpro = new BankProcess();
public void BankHandle(Client client)
{
switch (client .Type)
{
case "deposite": //存款
bankpro.Deposite();
break;
case "withdraw": //取款
bankpro.Withdraw();
break;
case "transfer": //转账
bankpro.Transfer();
break;
}
}
}
这种设计显然是存在问题的,目前设计中就只有存款,取款和转账三个功能,将来如果业务增加了,比如增加申购基金功能,理财功能等,就必须要修改BankProcess业务类。我们分析上述设计就能发现不能把业务封装在一个类里面,违反单一职责原则,而有新的需求发生,必须修改现有代码则违反了开放封闭原则。如何才能实现耦合度和灵活性兼得呢?
那就是抽象,将业务功能抽象为接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。
以下是符合OCP的设计:
//首先声明一个业务处理接口
public interface IBankProcess
{
void Process();
}
public class DeposiProcess:IBankProcess
{
public void Process() //办理存款业务
{
Console.WriteLine("Process Deposit");
}
}
public class WithDrawProcess:IBankProcess
{
public void Process() //办理取款业务
{
Console.WriteLine("Process WithDraw");
}
}
public class TransferProcess:IBankProcess
{
public void Process() //办理转账业务
{
Console .WriteLine ("Process Transfer");
}
}
public class BankStaff
{
private IBankProcess bankpro = null ;
public void BankHandle(Client client)
{
switch (client .Type)
{
case "Deposite": //存款
userProc =new WithDrawUser();
break;
case "WithDraw": //取款
userProc =new WithDrawUser();
break;
case "Transfer": //转账
userProc =new WithDrawUser();
break;
}
userProc.Process();
}
}
这样当业务变更时,只需要修改对应的业务实现类就可以,其他不相干的业务就不必修改。当业务增加,只需要增加业务的实现就可以了。1.3 实例
以开放-封闭原则实现一个播放器,这个播放器支持扩展,而如果想要扩展,即增加播放器的功能的话,无需修改到以前播放器其他的功能代码。
先定义一个抽象的接口:
2_10_PlayerProcess.php:
<?php
interface Process
{
public function process();
}
再来继承该接口,实现播放器的解码功能:2_10_PlayerPlayerencode.php:
<?php
/**
* 实现播放器解码的功能
*/
class Playerencode implements Process
{
public function process()
{
echo 'encode' . PHP_EOL;
}
}
2_10_PlayerPlayeroutput.php:
<?php
/**
* 实现播放器输出的功能
*/
class Playeroutput implements Process
{
public function process()
{
echo 'output' . PHP_EOL;
}
}
接着定义播放器的线程调度管理器,播放器一旦接收到通知(可以是外部单击行为也可以是内部的notify行为),将回调实际的线程处理:
2_10_PlayerPlayProcess.php:
<?php
/**
* 播放器的调度管理器,播放器一旦接收到通知(可以是外部单击行为也可以是内部的notify行为),将回调实际的线程处理
*/
class PlayProcess
{
private $message;
public function callback(event $event)
{
$this->message = $event->click();
if ($this->message instanceof Process) {
$this->message->process();
}
}
}
接着实现播放器的事件处理逻辑:
2_10_PlayerMP4.php:
<?php
/**
* 播放器的事件处理逻辑,定义事件的处理逻辑
*/
class MP4
{
public function work()
{
$PlayProcess = new PlayProcess();
$PlayProcess->callback(new Event('encode'));
$PlayProcess->callback(new Event('output'));
}
}
最后是实现为事件分拣的处理类,此类负责对事件进行分拣,判断用户或者内部行为,以产生正确的线程,供播放器内置的线程管理器调度:
2_10_PlayerEvent.php:
<?php
/**
* 播放器的事件处理类,此类对事件进行分拣,判断用户或者是内部行为,以产生正确的线程,供播放器内置的线程管理器调度。
*/
class Event
{
private $m;
public function __construct($me)
{
$this->m = $me;
}
public function click()
{
switch ($this->m) {
case 'encode':
$event = new Playerencode();
break;
case 'output':
$event = new Playeroutput();
break;
default:
$event = false;
break;
}
return $event;
}
}
最后可以写个代码运行一下:2_10_Playerexample.php:
<?php
/**
* 运行测试
*/
// 采用自动载入类,不用手动去require所需的类文件
spl_autoload_register('autoload');
function autoload($class)
{
require __DIR__.'/'.$class.'.php';
}
$mp4 = new MP4();
$mp4->work();
输出:encode
output
这就实现了一个基本的播放器,该播放器的功能模块是对外开放的,只需要实现process接口就能给播放器添加新的功能模块,而内部处理是相对封闭和稳定的。
可以参考一下流程图:
可以参考一下流程图: