面向对象的好习惯.docx
- 文档编号:28761072
- 上传时间:2023-07-19
- 格式:DOCX
- 页数:28
- 大小:28.53KB
面向对象的好习惯.docx
《面向对象的好习惯.docx》由会员分享,可在线阅读,更多相关《面向对象的好习惯.docx(28页珍藏版)》请在冰豆网上搜索。
面向对象的好习惯
如果您尚未打算用OO原则创建应用程序,则使用PHP的面向对象(OO)的语言特性,这7个习惯将帮助您开始在过程编程与OO编程之间进行转换。
在PHP编程早期,PHP代码在本质上是限于面向过程的。
过程代码的特征在于使用过程构建应用程序块。
过程通过允许过程之间的调用提供某种程度的重用。
但是,没有面向对象的语言构造,程序员仍然可以把OO特性引入到PHP代码中。
这样做有点困难并且会使代码难于阅读,因为它是混合范例(含有伪OO设计的过程语言)。
使用PHP代码中的OO构造—例如能够定义和使用类、能够构建使用继承的类之间的关系以及能够定义接口—可以更轻松地构建符合优秀OO实践的代码。
虽然没有过多模块化的纯过程设计运行得很好,但是OO设计的优点表现在维护上。
由于典型应用程序的大部分生命周期都花费在维护上,因此代码维护是应用程序生命周期的重要部分。
并且在开发过程中代码维护很容易被遗忘。
如果在应用程序开发和部署方面存在竞争,那么长期可维护性可能被放在比较次要的地位。
模块化—优秀OO设计的主要特性之一—可以帮助完成这样的维护。
模块化将帮助封装更改,这样可以随着时间的推移更轻松地扩展和修改应用程序。
总的来说,虽然构建OO软件的习惯不止7个,但是遵循这里的7个习惯可以使代码符合基本OO设计标准。
它们将为您提供更牢固的基础,在此基础之上建立更多OO习惯并构建可轻松维护与扩展的软件。
这些习惯针对模块化的几个主要特性。
有关独立于语言的OO设计优点的更多信息,请参阅参考资料。
7个优秀PHPOO习惯包括:
保持谦虚。
做个好邻居。
避免看到美杜莎。
利用最弱的链接。
您是橡皮;我是胶水。
限制传播。
考虑使用模式。
保持谦虚
保持谦虚指避免在类实现和函数实现中暴露自己。
隐藏您的信息是一项基本习惯。
如果不能养成隐藏实现细节的习惯,那么将很难养成任何其他习惯。
信息隐藏也称为封装。
直接公开公共字段是一个坏习惯的原因有很多,最重要的原因是让您在实现更改中没有应有的选择。
使用OO概念隔离更改,而封装在确保所作更改在本质上不是病毒性(viral)更改方面扮演不可或缺的角色。
病毒性更改是开始时很小的更改—如将保存三个元素的数组更改为一个只包含两个元素的数组。
突然,您发现需要更改越来越多的代码以适应本应十分微不足道的更改。
开始隐藏信息的一种简单方法是保持字段私有并且用公共访问方法公开这些字段,就像家中的窗户一样。
并没有让整面墙都朝外部开放,而只打开一两扇窗户(我将在“好习惯:
使用公共访问方法”中介绍访问方法的更多信息)。
除了允许您的实现隐藏在更改之后外,使用公共访问方法而非直接公开字段将允许您在基本实现的基础上进行构建,方法为覆盖访问方法的实现以执行略微不同于父方法的行为。
它还允许您构建一个抽象实现,从而使实际实现委托给覆盖基本实现的类。
坏习惯:
公开公共字段
在清单1的坏代码示例中,Person对象的字段被直接公开为公共字段而非使用访问方法。
虽然此行为十分诱人,尤其对于轻量级数据对象来说更是如此,但是它将对您提出限制。
清单1.公开公共字段的坏习惯
php
classPerson
{
public$prefix;
public$givenName;
public$familyName;
public$suffix;
}
$person=newPerson();
$person->prefix="Mr.";
$person->givenName="John";
echo($person->prefix);
echo($person->givenName);
?
>
如果对象有任何更改,则使用该对象的所有代码也都需要更改。
例如,如果某人的教名、姓氏和其他名字被封装到PersonName对象中,则需要修改所有代码以适应更改。
好习惯:
使用公共访问方法
通过使用优秀的OO习惯(参见清单2),同一个对象现在拥有私有字段而非公共字段,并且通过称为访问方法的get和set公共方法谨慎地向外界公开私有字段。
这些访问方法现在提供了一种从PHP类中获取信息的公共方法,这样在实现发生更改时,更改使用类的所有代码的需求很可能变小。
清单2.使用公共访问方法的好习惯
php
classPerson
{
private$prefix;
private$givenName;
private$familyName;
private$suffix;
publicfunctionsetPrefix($prefix)
{
$this->prefix=$prefix;
}
publicfunctiongetPrefix()
{
return$this->prefix;
}
publicfunctionsetGivenName($gn)
{
$this->givenName=$gn;
}
publicfunctiongetGivenName()
{
return$this->givenName;
}
publicfunctionsetFamilyName($fn)
{
$this->familyName=$fn;
}
publicfunctiongetFamilyName()
{
return$this->familyName;
}
publicfunctionsetSuffix($suffix)
{
$this->suffix=$suffix;
}
publicfunctiongetSuffix()
{
return$suffix;
}
}
$person=newPerson();
$person->setPrefix("Mr.");
$person->setGivenName("John");
echo($person->getPrefix());
echo($person->getGivenName());
?
>
乍看之下,这段代码可能会完成大量工作,并且实际上可能更多是在前端的工作。
但是,通常,使用优秀的OO习惯从长远来看十分划算,因为将极大地巩固未来更改。
在清单3中所示的代码版本中,我已经更改了内部实现以使用名称部件的关联数组。
比较理想的情况是,我希望拥有错误处理并且更仔细地检查元素是否存在,但是本例的目的在于展示使用我的类的代码无需更改的程度—代码并没有察觉到类发生更改。
记住采用OO习惯的原因是要谨慎封装更改,这样代码将更具有可扩展性并且更容易维护。
清单3.使用不同内部实现的另一个示例
php
classPerson
{
private$personName=array();
publicfunctionsetPrefix($prefix)
{
$this->personName['prefix']=$prefix;
}
publicfunctiongetPrefix()
{
return$this->personName['prefix'];
}
publicfunctionsetGivenName($gn)
{
$this->personName['givenName']=$gn;
}
publicfunctiongetGivenName()
{
return$this->personName['givenName'];
}
/*etc...*/
}
/*
*Eventhoughtheinternalimplementationchanged,thecodeherestaysexactly
*thesame.ThechangehasbeenencapsulatedonlytothePersonclass.
*/
$person=newPerson();
$person->setPrefix("Mr.");
$person->setGivenName("John");
echo($person->getPrefix());
echo($person->getGivenName());
?
>
回页首
做个好邻居
在构建类时,它应当正确地处理自己的错误。
如果该类不知道如何处理错误,则应当以其调用者理解的格式封装这些错误。
此外,避免返回空对象或者状态无效的对象。
许多时候,只需通过检验参数并抛出特定异常说明提供参数无效的原因就可以实现这一点。
在您养成这个习惯时,它可以帮您—和维护代码或使用对象的人员—节省很多时间。
坏习惯:
不处理错误
考虑清单4中所示的示例,该示例将接受一些参数并返回填充了一些值的Person对象。
但是,在parsePersonName()方法中,没有验证提供的$val变量是否为空、是否是零长度字符串或者字符串是否使用无法解析的格式。
parsePersonName()方法不返回Person对象,但是返回null。
使用这种方法的管理员或程序员可能会觉得很麻烦—至少他们现在需要开始设置断点并调试PHP脚本。
清单4.不抛出或处理错误的坏习惯
classPersonUtils
{
publicstaticfunctionparsePersonName($format,$val)
{
if(strpos(",",$val)>0){
$person=newPerson();
$parts=split(",",$val);//Assumethevalueislast,first
$person->setGivenName($parts[1]);
$person->setFamilyName($parts[0]);
}
return$person;
}
}
清单4中的parsePersonName()方法可以修改为在if条件外部初始化Person对象,确保总是获得有效的Person对象。
但是,您得到的是没有set属性的Person,这仍然没有很好地改善您的困境。
好习惯:
每个模块都处理自己的错误
不要让调用方凭空猜测,而是对参数进行预先验证。
如果未设置的变量无法生成有效的结果,请检查变量并抛出InvalidArgumentException。
如果字符串不能为空或者必须为特定格式,请检查格式并抛出异常。
清单5解释了如何在演示一些基本验证的parsePerson()方法中创建异常以及一些新条件。
清单5.抛出错误的好习惯
php
classInvalidPersonNameFormatExceptionextendsLogicException{}
classPersonUtils
{
publicstaticfunctionparsePersonName($format,$val)
{
if(!
$format){
thrownewInvalidPersonNameFormatException("InvalidPersonNameformat.");
}
if((!
isset($val))||strlen($val)==0){
thrownewInvalidArgumentException("Mustsupplyanon-nullvaluetoparse.");
}
}
}
?
>
最终目的是希望人们能够使用您的类,而不必了解其中的工作原理。
如果他们使用的方法不正确或者不是按照期望的方法使用,也不需要猜测不能工作的原因。
作为一个好邻居,您需要知道对您的类进行重用的人并没有特异功能,因此您需要解决猜测的问题。
回页首
避免看到美杜莎
在我最初了解OO概念时,我十分怀疑接口是否真正有帮助。
我的同事给我打了个比方,说不使用接口就好像看到美杜莎的头。
在希腊神话中,美杜莎是长着蛇发的女怪。
凡是看了她一眼的人都会变成石头。
杀死美杜莎的珀尔休斯通过在盾上观察她的影子,避免了变成石头而得以与她对抗。
接口就是对付美杜莎的镜子。
当您使用一个特定的具体实现时,代码也必须随着实现代码的更改而更改。
直接使用实现将限制您的选择,因为您已经在本质上把类变成了“石头”。
坏习惯:
不使用接口
清单6显示了从数据库中装入Person对象的示例。
它将获取人员的姓名并返回数据库中匹配的Person对象。
清单6.不使用接口的坏习惯
php
classDBPersonProvider
{
publicfunctiongetPerson($givenName,$familyName)
{
/*gotothedatabase,gettheperson...*/
$person=newPerson();
$person->setPrefix("Mr.");
$person->setGivenName("John");
return$person;
}
}
/*Ineedtogetpersondata...*/
$provider=newDBPersonProvider();
$person=$provider->getPerson("John","Doe");
echo($person->getPrefix());
echo($person->getGivenName());
?
>
在环境发生更改之前,从数据库中装入Person的代码都可以正常运行。
例如,从数据库装入Person可能适用于第一个版本的应用程序,但是对于第二个版本,可能需要添加从Web服务装入人员的功能。
其实,该类已经变成“石头”,因为它在直接使用实现类并且现在能做的更改十分有限。
好习惯:
使用接口
清单7显示了一个代码示例,在实现了加载用户的新方法后并没有进行更改。
该示例显示了一个名为PersonProvider的接口,该接口将声明单个方法。
如果任何代码使用PersonProvider,代码都禁止直接使用实现类。
相反,它就像是一个实际对象一样使用PersonProvider。
清单7.使用接口的好习惯
php
interfacePersonProvider
{
publicfunctiongetPerson($givenName,$familyName);
}
classDBPersonProviderimplementsPersonProvider
{
publicfunctiongetPerson($givenName,$familyName)
{
/*pretendtogotothedatabase,gettheperson...*/
$person=newPerson();
$person->setPrefix("Mr.");
$person->setGivenName("John");
return$person;
}
}
classPersonProviderFactory
{
publicstaticfunctioncreateProvider($type)
{
if($type=='database')
{
returnnewDBPersonProvider();
}else{
returnnewNullProvider();
}
}
}
$config='database';
/*Ineedtogetpersondata...*/
$provider=PersonProviderFactory:
:
createProvider($config);
$person=$provider->getPerson("John","Doe");
echo($person->getPrefix());
echo($person->getGivenName());
?
>
在使用接口时,尝试避免直接引用实现类。
相反,使用对象外部的内容可以提供正确的实现。
如果您的类将装入基于某些逻辑的实现,它仍然需要获取所有实现类的定义,并且那样做也无法取得任何效果。
您可以使用Factory模式来创建实现接口的实现类的实例。
根据约定,factory方法将以create为开头并返回接口。
它可以为您的factory获取必要的参数以计算出应当返回哪个实现类。
在清单7中,createProvider()方法只是获取$type。
如果$type被设为database,工厂将返回DBPersonProvider的实例。
从数据库中装入人员的任何新实现都不要求在使用工厂和接口的类中进行任何更改。
DBPersonProvider将实现PersonProvider接口并且拥有getPerson()方法的实际实现。
回页首
利用最弱的链接
将模块松散耦合在一起是件好事情;它是允许您封装更改的属性之一。
另外两个习惯—“保持谨慎”和“避免看到美杜莎”—可帮助您构建松散耦合的模块。
要实现松散耦合的类,可通过养成降低类依赖关系的习惯实现。
坏习惯:
紧密耦合
在清单8中,降低依赖关系并不是必须降低使用对象的客户机的依赖关系。
相反,该示例将演示如何降低与正确类的依赖关系并最小化这种依赖关系。
清单8.Address中紧密耦合的坏习惯
php
require_once"./AddressFormatters.php";
classAddress
{
private$addressLine1;
private$addressLine2;
private$city;
private$state;//orprovince...
private$postalCode;
private$country;
publicfunctionsetAddressLine1($line1)
{
$this->addressLine1=$line1;
}
/*accessors,etc...*/
publicfunctiongetCountry()
{
return$this->country;
}
publicfunctionformat($type)
{
if($type=="inline"){
$formatter=newInlineAddressFormatter();
}elseif($type=="multiline"){
$formatter=newMultilineAddressFormatter();
}else{
$formatter=newNullAddressFormatter();
}
return$formatter->format($this->getAddressLine1(),
$this->getAddressLine2(),
$this->getCity(),$this->getState(),$this->getPostalCode(),
$this->getCountry());
}
}
$addr=newAddress();
$addr->setAddressLine1("123AnySt.");
$addr->setAddressLine2("Ste200");
$addr->setCity("Anytown");
$addr->setState("AY");
$addr->setPostalCode("55555-0000");
$addr->setCountry("US");
echo($addr->format("multiline"));
echo("\n");
echo($addr->format("inline"));
echo("\n");
?
>
在Address对象上调用format()方法的代码可能看上去很棒—这段代码所做的是使用Address类,调用format()并完成。
相反,Address类就没那么幸运。
它需要了解用于正确格式化的各种格式化方法,这可能使Address对象无法被其他人很好地重用,尤其是在其他人没有兴趣在format()方法中使用格式化方法类的情况下。
虽然使用Address的代码没有许多依赖关系,但是Address类却有大量代码,而它可能只是一个简单的数据对象。
Address类与知道如何格式化Address对象的实现类紧密耦合。
好习惯:
在对象之间松散耦合
在构建优秀的OO设计时,必须考虑称为关注点分离(SeparationofConcerns,SoC)的概念。
SoC指尝试通过真正关注的内容分离对象,从而降低耦合度。
在最初的Address类中,它必须关注如何进行格式化。
这可能不是优秀的设计。
然而,Address类应当考虑Address的各部分,而某种格式化方法应当关注如何正确格式化地址。
在清单9中,格式化地址的代码被移到接口、实现类和工厂中—养成“使用接口”的习惯。
现在,AddressFormatUtils类负责创建格式化方法并格式化Address。
任何其他对象现在都可以使用Address而不必担心要求获得格式化方法的定义。
清单9.在对象之间松散耦合的好习惯
php
interfaceAddressFormatter
{
publicfunctionformat($addressLine1,$addressLine2,$city,$state,
$postalCode,$country);
}
classMultiLineAddressFormatterimplementsAddressFormatter
{
publicfunctionformat($addressLine1,$addressLine2,$city,$state,
$postalCode,$country)
{
returnsprintf("%s\n%s\n%s,%s%s\n%s",
$addressLine1,$addressLine2,$city,$state,$postalCode,$country);
}
}
classInlin
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 面向 对象 习惯
![提示](https://static.bdocx.com/images/bang_tan.gif)