<<深入PHP面向对象、模式与实践>>读书笔记:面向对象设计和过程式编程

栏目: PHP · 发布时间: 5年前

内容简介:注:本文内容来<<深入PHP面向对象、模式与实践>>中6.2节。面向对象设计和过程式编程有什么不同呢?可能有些人认为最大的不同在于面向对象编程中包含对象。事实上,这种说法不准确。在PHP中,你经常会发现过程式编程也使用对象,如使用一个数据库类,也可能遇到类中包含过程式代码的情况。类的出现并不能说明使用了面向对象设计。甚至对于Java这种强制把一切都包含在类中的语音(这个我可以证明,我在大三的时候学过Java),使用对象也不能说明使用了面向对象设计。面向对象编程和过程式编程的一个核心区别是如何

注:本文内容来<<深入 PHP 面向对象、模式与实践>>中6.2节。

6.2 面向对象设计与过程式编程

  面向对象设计和过程式编程有什么不同呢?可能有些人认为最大的不同在于面向对象编程中包含对象。事实上,这种说法不准确。在PHP中,你经常会发现过程式编程也使用对象,如使用一个数据库类,也可能遇到类中包含过程式代码的情况。类的出现并不能说明使用了面向对象设计。甚至对于 Java 这种强制把一切都包含在类中的语音(这个我可以证明,我在大三的时候学过Java),使用对象也不能说明使用了面向对象设计。

  面向对象编程和过程式编程的一个核心区别是如何 分配职责 。过程式编程表现为一系列命令和方法的连续调用。控制代码根据 不同的条件执行不同的职责 。这种自顶向下的控制方式导致了 重复和相互依赖的代码遍布于整个项目 。面向对象编程则将职责从客户端代码中移到专门的对象中,尽量减少相互依赖。

  为了说明以上几点,我们分别使用面向对象和过程式代码的方式来分析一个简单的问题。假设我们要创建一个用于读写配置文件的工具。为了重点关注代码的结构,示例中将忽略具体的功能实现。(文后有完整代码示例,来自于 图灵社区 )

  我们先按过程式方式来解决这个问题。首先,用下面的格式来读写文本:

key:value

只需要两个函数:

function readParams( $sourceFile ) {
    $params = array();
    // 从$sourceFile中读取文本参数
    return $params;
}

function writeParams( $params, $sourceFile ) {
    // 写入文本参数到$sourceFile
}

readParams()函数的参数为源文件的名称。该函数试图打开文件,读取每一行内容并查找键/值对,然后用键/值对构建一个关联数组。最后,该函数给控制代码返回数组。writeParams()以关联数组和指向源文件的路径作为参数,它循环遍历关联数组,将每对键/值对写入文件。下面是使用这两个函数的客户端代码:

$file = './param.txt';
$array['key1'] = 'vall';
$array['key2'] = 'val2';
$array['key3'] = 'val3';
writeParams( $array, $file );
$output = readParams( $file );
print_r( $output );

这段代码较为紧凑并且易于维护。writeParams()被调用来创建Param.txt并向其写入如下的内容:

key1:val1

key2:val2

key3:val3

现在,我们被告知这个 工具 需要支持如下所示XML格式:

<params>
    <param>
        <key>my key</key>
        <val>my val</val>
    </param>
</params>

  如果参数文件以.xml文件结尾,就应该以XML模式读取参数文件。虽然这不难调节,但可能会使我们的代码更难维护。这是我们有两个选择:可以在控制代码中检查文件扩展名,或者在读写函数中检测。我们使用后面那种写法。:

function readParams( $source ) {
    $params = array();
    if ( preg_match( "/\.xml$/i", $source ) ) {
        // 从$source中读取XML参数
    } else {
        // $source中读取文本参数
    }
    return $params;
}

function writeParams( $params, $source ) {
    if ( preg_match( "/\.xml$/i", $source ) ) {
        // 写入XML参数到$source
    } else {
        // 写入文本参数到$source
    }
}

  如上所示,我们在两个函数中都要检查XML扩展名,这样的重复性代码会产生问题。如果我们还被要求支持其他格式的参数,就要保持readParams()和writeParams()函数的一致性。

  下面我们用类来处理相同的问题。首先,创建一个抽象的基类来定义类型接口:

abstract class ParamHandler {
    protected $source;
    protected $params = array();

    function __construct( $source ) {
        $this->source = $source;
    }

    function addParam( $key, $val ) {
        $this->params[$key] = $val;
    }

    function getAllParams() {
        return $this->params;
    }

    static function getInstance( $filename ) {
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        return new TextParamHandler( $filename );
    }

    abstract function write();
    abstract function read();
}

  我们定义addParam()方法来允许用户增加参数到protected属性$params, getAllParams()则用于访问该属性,获得$params的值。

  我们还创建了静态的getInstance()方法来检测文件扩展名,并根据文件扩展名返回特定的子类。最重要的是,我们定义了两个抽象方法read()和write(),确保ParamHandler类的任何子类都支持这个接口。

  现在,我们定义了多个子类。为了实例简洁,再次忽略实现细节:

class XmlParamHandler extends ParamHandler {

    function write() {
        // 写入XML文件
        // 使用$this->params
    }

    function read() {
        // 读取XML文件内容
        // 并赋值给$this->params
    } 

}

class TextParamHandler extends ParamHandler {

    function write() {
        // 写入文本文件
        // 使用$this->params
    }

    function read() {
        // 读取文本文件内容
        // 并赋值给$this->params
    } 

}

  这些类简单地提供了write()和read()方法的实现。每个类都将根据适当的文件格式进行读写。客户端代码将完全自动地根据文件扩展名来写入数据到文本和XML格式的文件:

$file = "./params.xml"; 
$test = ParamHandler::getInstance( $file );
$test->addParam("key1", "val1" );
$test->addParam("key2", "val2" );
$test->addParam("key3", "val3" );
$test->write(); // 写入XML格式中

我们还可以从两种文件格式中读取:

$test = ParamHandler::getInstance( "./params.txt" );
$test->read(); // 从文本格式中读取

那么,我们可以从这两种解决方案中学习到什么呢?

职责

  在过程式编程的例子中,控制代码的职责(duties)是判断文件格式,它判断了两次而不是一次。条件语句被绑定到函数中,但这仅是将判断的流程影藏起来。对readParams()的调用和对writeParams()的调用必须发生在不同的地方,因此我们不得不在每个函数中重复检测文件扩展名(或执行其他检测操作)。

  在面向对象代码中,我们在静态方法getInstance()中进行文件格式的选择,并且仅在getInstance()中检测文件扩展名一次,就可以决定使用哪一个合适的子类。客户端代码并不负责实现读写功能。它 不需要知道自己属于哪个子类就可以使用给定的对象 。它 只需要知道自己在使用ParamHandler对象 ,并且ParamHandler对象支持write()和read()的方法。过程式代码忙于处理细节,而面向对象代码 只需一个接口 即可工作,并且不要考虑实现的细节。由于实现由对象负责,而不是由客户端代码负责,所以我们能够很方便地增加对新格式的支持。

内聚

  内聚(cohesion)是一个模块内部各成分之间相互关联程度的度量。理想情况下,你应该使 各个组件职责清晰、分工明确 。如果代码间的关联范围太广,维护就会很困难--因为你需要在 修改部分代码的同时修改相关代码

  前面的ParamHandler类将相关的处理过程集中起来。用于处理XML的类方法间可以共享数据,并且一个类方法中的改变可以很容易地反映到另一个方法中(比如改变XML元素名)。因此我们可以说ParamHandler类是高度内聚的。

  另一方面,过程式的例子则把相关的过程分离开,导致处理XML的代码在多个函数中同时出现。

耦合

  当系统各部分代码紧密绑在一起时,就会产生精密耦合(coupling),这时在一个组件中的变化会迫使其他部件随之改变。紧密耦合不是过程式代码特有的,但是过程式代码比较容易产生耦合问题。

  我们可以在过程代码中看到耦合的产生。在writeParams()和readParams()函数中,使用了相同的文件扩展名测试来决定如何处理数据。因此我们要改下一个函数,就不得不同时改写另一个函数。例如,我们要增加一种新的文件格式,就要在两个函数中按相同的方式都加上相应的扩展名检查代码,这样两个函数才能保持一致。

  面向对象的示例中则将每个子类彼此分开,也将其余客户端代码分开。如果需要增加新的参数格式,只需简单地创建相应的子类,并在父类的静态方法getInstance()中增加一行文件检测代码即可。

正交

  (orthogonality)指将职责相关的组件紧紧结合在一起,而与外部系统环境隔开,保持独立。在<<The Pragmatic Programmer>>(中文名<<程序员修炼之道:从小工到专家 >>)一书中有所介绍。

  正交主张重用组件,期望 不需要任何特殊配置就能把一个组件插入到新系统 中。这样的组件有明确的与环境无关的输入和输出。正交代码使修改变得更简单,因为修改一个实现只会影响到被改动的组件本身。最后,正交代码更加安全。bug的影响 只局限于它的作用域之中 。内部高度相互依赖的代码发生错误时,很容易在系统中引起连锁反应。

  如果只有一个类,松散耦合和高聚合是无从谈起的。毕竟,我们可以把整个过程示例的全部代码塞到一个被误导的类里。(这想想就挺可怕的。)

职责和耦合的英文翻译原文是没有的,我通过 Goole翻译 加上的。

代码示例

过程式编程

<?php

$file = "./texttest.proc.xml"; 
$array['key1'] = "val1";
$array['key2'] = "val2";
$array['key3'] = "val3";
writeParams( $array, $file );
$output = readParams( $file );
print_r( $output ); 

function readParams( $source ) {
    $params = array();
    if ( preg_match( "/\.xml$/i", $source )) {
        $el = simplexml_load_file( $source ); 
        foreach ( $el->param as $param ) {
            $params["$param->key"] = "$param->val";
        }
    } else {
        $fh = fopen( $source, 'r' );
        while ( ! feof( $fh ) ) {
            $line = trim( fgets( $fh ) );
            if ( ! preg_match( "/:/", $line ) ) {
                continue;
            }
            list( $key, $val ) = explode( ':', $line );
            if ( ! empty( $key ) ) {
                $params[$key]=$val;
            }
        }
        fclose( $fh );
    }
    return $params;
}

function writeParams( $params, $source ) {
    $fh = fopen( $source, 'w' );
    if ( preg_match( "/\.xml$/i", $source )) {
        fputs( $fh, "<params>\n" );
        foreach ( $params as $key=>$val ) {
            fputs( $fh, "\t<param>\n" );
            fputs( $fh, "\t\t<key>$key</key>\n" );
            fputs( $fh, "\t\t<val>$val</val>\n" );
            fputs( $fh, "\t</param>\n" );
        }
        fputs( $fh, "</params>\n" );
    } else {
        foreach ( $params as $key=>$val ) {
            fputs( $fh, "$key:$val\n" );
        }
    }
    fclose( $fh );
}

面向对象设计

<?php
abstract class ParamHandler {
    protected $source;
    protected $params = array();

    function __construct( $source ) {
        $this->source = $source;
    }

    function addParam( $key, $val ) {
        $this->params[$key] = $val;
    }

    function getAllParams() {
        return $this->params;
    }

    protected function openSource( $flag ) {
        $fh = @fopen( $this->source, $flag );
        if ( empty( $fh ) ) {
            throw new Exception( "could not open: $this->source!" );
        }
        return $fh;
    }

    static function getInstance( $filename ) {
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        return new TextParamHandler( $filename );
    }

    abstract function write();
    abstract function read();
}

class XmlParamHandler extends ParamHandler {

    function write() {
        $fh = $this->openSource('w');
        fputs( $fh, "<params>\n" );
        foreach ( $this->params as $key=>$val ) {
            fputs( $fh, "\t<param>\n" );
            fputs( $fh, "\t\t<key>$key</key>\n" );
            fputs( $fh, "\t\t<val>$val</val>\n" );
            fputs( $fh, "\t</param>\n" );
        }
        fputs( $fh, "</params>\n" );
        fclose( $fh );
        return true;
    }

    function read() {
        $el = @simplexml_load_file( $this->source ); 
        if ( empty( $el ) ) { 
            throw new Exception( "could not parse $this->source" );
        } 
        foreach ( $el->param as $param ) {
            $this->params["$param->key"] = "$param->val";
        }
        return true;
    } 

}

class TextParamHandler extends ParamHandler {

    function write() {
        $fh = $this->openSource('w');
        foreach ( $this->params as $key=>$val ) {
            fputs( $fh, "$key:$val\n" );
        }
        fclose( $fh );
        return true;
    }

    function read() {
        $lines = file( $this->source );
        foreach ( $lines as $line ) {
            $line = trim( $line );
            list( $key, $val ) = explode( ':', $line );
            $this->params[$key]=$val;
        }
        return true;
    } 

}

//$file = "./texttest.xml"; 
$file = "./texttest.txt"; 
$test = ParamHandler::getInstance( $file );
$test->addParam("key1", "val1" );
$test->addParam("key2", "val2" );
$test->addParam("key3", "val3" );
$test->write();

$test = ParamHandler::getInstance( $file );
$test->read();

$arr = $test->getAllParams();
print_r( $arr );

本文为作者自己读书总结的文章,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机算法导引

计算机算法导引

卢开澄 / 第2版 (2006年1月1日) / 2006-1 / 38.0

本书为《计算机算法导引——设计与分析》的第2版。书中内容分3部分:第1部分是基本算法,按方法论区分,包含优先策略与分治策略、动态规划、概率算法、并行算法、搜索法、数据结构等;第2部分是若干专题,包括排序算法、计算几何及计算数论、线性规划;第3部分是复杂性理论与智能型算法,其中,智能型算法主要介绍了遗传算法和模拟退火算法。本书可作为计算机系本科学生及研究生教材,数学系师生和科研T作者也可将其作为参考......一起来看看 《计算机算法导引》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

html转js在线工具
html转js在线工具

html转js在线工具