注:本文内容来自<<深入PHP面向对象第6.2节,模式与实践>>。6.2面向对象设计和过程式编程面向对象设计和过程式编程有什么区别?有些人可能认为最大的区别是面向对象编程中包含了对象。其实,这种说法并不准确。在PHP中,您会经常发现使用对象(例如数据库类)进行过程化编程,您也可能会在类中遇到过程化代码。类的存在并不表示使用了面向对象的设计。即使是强制一切都包含在类中的Java(我可以证明这一点,我大三学的Java),使用对象并不意味着使用面向对象设计。面向对象编程和过程编程之间的核心区别在于如何分配职责。过程编程表现为一系列命令和方法的顺序调用。控制代码根据不同的情况执行不同的职责。这种自上而下的控制方法导致整个项目中代码的重复和相互依赖。面向对象的编程将责任从客户端代码转移到专门的对象,从而最大限度地减少相互依赖性。为了说明以上几点,我们用面向对象和过程化的代码来分析一个简单的问题。假设我们要创建一个读写配置文件的工具。为了关注代码的结构,示例中将忽略具体的功能实现。(文末有完整的代码示例,来自图灵社区)让我们用程序化的方式来解决这个问题。首先,使用如下格式读写文本:key:value只需要两个函数:functionreadParams($sourceFile){$params=array();//从$sourceFile中读取文本参数return$params;}functionwriteParams($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:val1key2:val2key3:val3现在,我们被告知该工具需要支持以下XML格式:mykeymyval如果参数文件以.xml文件结尾,则需要以XML方式读取参数文件。虽然这并不难调整,但它可能会使我们的代码更难维护。这里我们有两个选择:我们可以在控制代码中查看文件扩展名,或者在读写函数中查看。我们使用后一种表示法。:functionreadParams($source){$params=数组();if(preg_match("/\.xml$/i",$source)){//从$source读取XML参数}else{//$从source读取文本参数}return$params;}functionwriteParams($params,$source){if(preg_match("/\.xml$/i",$source)){//将XML参数写入$source}else{//将文本参数写入$source}}如上所示,我们检查对于这两个函数中的XML扩展,这种重复的代码会导致问题。如果我们还需要支持其他格式的参数,就要保持readParams()和writeParams()函数的一致性。下面我们使用类来处理同样的问题。首先,创建一个抽象基类来定义类型接口:abstractclassParamHandler{protected$source;受保护的$params=array();函数__construct($source){$this->source=$source;}functionaddParam($key,$val){$this->params[$key]=$val;}函数getAllParams(){返回$this->params;}staticfunctiongetInstance($filename){if(preg_match("/\.xml$/i",$filename)){returnnewXmlParamHandler($filename);}返回新的TextParamHandler($filename);}抽象函数write();abstractfunctionread();}我们定义addParam()方法允许用户向受保护的属性$params添加参数,getAllParams()用于访问这个属性并获取$params的值。我们还创建了静态getInstance()方法来检测文件扩展名并根据文件扩展名返回特定的子类。最重要的是,我们定义了两个抽象方法read()和write(),确保ParamHandler类的任何子类都支持这个接口。现在,我们定义多个子类。为简洁起见,再次忽略实现细节:赋值Give$this->params}}classTextParamHandlerextendsParamHandler{functionwrite(){//写入文本文件//使用$this->params}functionread(){//读取文本文件内容//并赋值给$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");$测试->读取();//从文本格式读取那么,我们可以从这两个解决方案中学到什么?职责在过程编程示例中,控制代码的职责是确定文件格式,确定两次而不是一次。条件语句绑定函数,但这只是隐藏了判断的流程。readParams()的调用和writeParams()的调用必须发生在不同的地方,因此我们必须在每个函数中重复检测文件扩展名(或执行其他检测操作)。在面向对象的代码中,我们在静态方法getInstance()中选择文件格式,并且在getInstance()中只检测一次文件扩展名,以决定使用哪个合适的子类。客户端代码不负责实现读写功能。它不需要知道它属于哪个子类就可以使用给定的对象。它只需要知道它正在使用ParamHandler对象,ParamHandler对象支持write()和read()方法。过程代码忙于处理细节,而面向对象的代码只使用一个接口而不用担心实现细节。由于实现是对象的责任,而不是客户端代码,我们可以轻松地添加对新格式的支持。CohesionCohesion是衡量模块内组件之间相互关系程度的指标。理想情况下,您应该明确每个组件的职责和分工。如果代码的范围关联太广,维护起来会很困难——因为你需要在修改相关代码的同时修改部分代码。前面的ParamHandler类将相关的处理过程集合在一起。处理XML的类方法可以共享数据,一个类方法中的更改可以很容易地反映到另一个方法中(例如更改XML元素名称)。因此我们可以说ParamHandler类是高度内聚的。另一方面,过程化的例子将相关的过程分开,导致XML处理代码同时出现在多个函数中。耦合耦合发生在系统代码的各个部分紧密结合在一起时,其中一个组件的变化会迫使其他部分发生变化。紧耦合并不是过程式代码独有的,但是过程式代码更容易出现耦合问题。我们可以看到在过程代码中创建的耦合。在writeParams()和readParams()函数中,使用相同的文件扩展名测试来决定如何处理数据。所以如果我们要改下一个函数,就得同时重写另一个函数。比如我们要增加一个新的文件格式,就需要在两个函数中以同样的方式添加相应的扩展名检查代码,这样两个函数才能保持一致。面向对象的示例将每个子类彼此分开,并与其余的客户端代码分开。如果需要添加新的参数格式,只需创建一个对应的子类,在父类的静态方法getInstance()中添加一行文件检测代码即可。正交性是指将与职责相关的组件紧密组合在一起,同时与外部系统环境分离并保持独立性。书中介绍了<>(中文名《程序员的修行之道:从小工到专家>>》)。正交性提倡复用组件,期望一个组件不需要任何特殊配置就可以插入到一个新的系统中。这些组件具有明确的环境独立输入和输出。正交代码使修改更容易,因为更改实现只会影响更改的组件本身。最后,正交码更安全。错误的影响仅限于其范围。当高度依赖的代码出现错误时,很容易在系统中引起连锁反应。如果只有一个类,松耦合和高聚合就无从谈起。毕竟,我们可以将整个流程示例的全部代码塞进一个错误的类中。(这个想想就吓人。)没有responsibility和coupling的英文原版翻译,我是通过goole翻译补上的。代码显示示例过程序格式paramas$param){$params["$param->key"]="$param->val";}}else{$fh=fopen($source,'r');while(!feof($fh)){$line=trim(fgets($fh));如果(!preg_match("/:/",$line)){继续;}list($key,$val)=explode(':',$line);如果(!empty($key)){$params[$key]=$val;}}fclose($fh);}return$params;}functionwriteParams($params,$source){$fh=fopen($source,'w');如果(preg_match("/\.xml$/i",$source)){fputs($fh,"\n");foreach($paramsas$key=>$val){fputs($fh,"\t\n");fputs($fh,"\t\t$key\n");fputs($fh,"\t\t$val\n");fputs($fh,"\t\n");}fputs($fh,"\n");}else{foreach($paramsas$key=>$val){fputs($fh,"$key:$val\n");}}fclose($fh);}面向图形设计source=$source;}functionaddParam($key,$val){$this->params[$key]=$val;}functiongetAllParams(){返回$this->params;}protectedfunctionopenSource($flag){$fh=@fopen($this->source,$flag);if(empty($fh)){thrownewException(“无法打开:$this->source!”);}返回$fh;}staticfunctiongetInstance($filename){if(preg_match("/\.xml$/i",$filename)){returnnewXmlParamHandler($filename);}返回新的TextParamHandler($filename);}抽象函数write();抽象函数read();}classXmlParamHandlerextendsParamHandler{functionwrite(){$fh=$this->openSource('w');fputs($fh,"<参数>\n");foreach($this->paramsas$key=>$val){fputs($fh,"\t\n");fputs($fh,"\t\t$key\n");fputs($fh,"\t\t$val\n");fputs($fh,"\t\n");}fputs($fh,"\n");fclose($fh);返回真;}functionread(){$el=@simplexml_load_file($this->source);if(empty($el)){thrownewException("无法解析$this->source");}foreach($el->paramas$param){$this->params["$param->key"]="$param->val";}返回真;}}classTextParamHandlerextendsParamHandler{functionwrite(){$fh=$this->openSource('w');foreach($this->paramsas$key=>$val){fputs($fh,"$key:$val\n");}fclose($fh);返回真;}functionread(){$lines=file($this->source);foreach($linesas$line){$line=trim($line);列表($key,$val)=explode(':',$line);$this->params[$key]=$val;}返回真;}//$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);本文是作者自己阅读的总结。由于作者水平所限,难免有错误。欢迎指正,非常感谢