【开放-封闭原则】使用开放封闭原则开发实例【原创】

摘要:主要是参考列旭松、陈文著的《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_Player\Process.php:
<?php

interface Process
{
    public function process();
}
再来继承该接口,实现播放器的解码功能:
2_10_Player\Playerencode.php:
<?php
/**
 * 实现播放器解码的功能
 */

class Playerencode implements Process
{
    public function process()
    {
        echo 'encode' . PHP_EOL;
    }
}

以及实现播放器的输出功能:
2_10_Player\Playeroutput.php:
<?php
/**
 * 实现播放器输出的功能
 */

class Playeroutput implements Process
{
    public function process()
    {
        echo 'output' . PHP_EOL;
    }
}

接着定义播放器的线程调度管理器, 播放器一旦接收到通知(可以是外部单击行为也可以是内部的notify行为),将回调实际的线程处理:
2_10_Player\PlayProcess.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_Player\MP4.php:
<?php
/**
 * 播放器的事件处理逻辑,定义事件的处理逻辑
 */

class MP4
{
    public function work()
    {
        $PlayProcess = new PlayProcess();
        $PlayProcess->callback(new Event('encode'));
        $PlayProcess->callback(new Event('output'));
    }
}

最后是实现为事件分拣的处理类,此类负责对事件进行分拣,判断用户或者内部行为,以产生正确的线程,供播放器内置的线程管理器调度:
2_10_Player\Event.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_Player\example.php:
<?php
/**
 * 运行测试
 */

// 采用自动载入类,不用手动去require所需的类文件
spl_autoload_register('autoload');

function autoload($class)
{
    require __DIR__.'/'.$class.'.php';
}

$mp4 = new MP4();
$mp4->work();
输出:
encode
output

这就实现了一个基本的播放器,该播放器的功能模块是对外开放的,只需要实现process接口就能给播放器添加新的功能模块,而内部处理是相对封闭和稳定的。

可以参考一下流程图:

发布了64 篇原创文章 · 获赞 47 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/jiandanokok/article/details/76214141
今日推荐