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


    摘要:主要是参考列旭松、陈文著的《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接口就能给播放器添加新的功能模块,而内部处理是相对封闭和稳定的。

    可以参考一下流程图:

  • 相关阅读:
    python eval() 进行条件匹配
    spring boot 学习
    JAVA基础
    在mac上进行JAVA开发
    移动端开发基础【8】页面生命周期
    数据挖掘【1】概述(引言)
    项目管理【26】 | 项目成本管理-规划成本管理
    项目管理【24】 | 项目进度管理-控制进度
    项目管理【25】 | 项目成本管理-成本管理概念
    操作系统【8】 Linux虚拟内存和物理内存
  • 原文地址:https://www.cnblogs.com/linewman/p/9918107.html
Copyright © 2020-2023  润新知