7 个优秀 PHP OO 习惯包括:
- 保持谦虚。---->良好的封装
- 做个好邻居。---->正确处理错误,对调用者提供错误标示
- 避免看到美杜莎。----->接口的好处
- 利用最弱的链接。----->降低类之间的耦合
- 您是橡皮;我是胶水。----->增强类的内聚力
- 限制传播。------>防止黏贴和复制,善用类的继承
- 考虑使用模式。
面向对象中很重要的一点就是信息的隐藏——封装。PHP的面向对象给开发者带来灵活性的同时也给很多的初学者带来了不规范性。我们在开发的过程中当要用到类中的信息时,我们通常就直接操作对象的属性来取得或者设置其值。还有我们很习惯在类的函数中临时创建类属性。这些都不是好习惯。
直接公开公共字段是一个坏习惯。原因有很多,最重要的原因是让你在实现更改中没有应有的选择。使用 OO 概念隔离更改,而封装在确保所作更改在本质上不是病毒性(viral)更改方面扮演不可或缺的角色。病毒性 更改是开始时很小的更改 — 如将保存三个元素的数组更改为一个只包含两个元素的数组。突然,您发现需要更改越来越多的代码以适应本应十分微不足道的更改。
如果对象有任何更改,则使用该对象的所有代码也都需要更改。例如,如果某人的教名、姓氏和其他名字被封装到PersonName
对象中,则需要修改所有代码以适应更改。
好习惯:使用公共访问方法
清单 2. 使用公共访问方法的好习惯
乍看之下,这段代码可能会完成大量工作,并且实际上可能更多是在前端的工作。但是,通常,使用优秀的 OO 习惯从长远来看十分划算,因为将极大地巩固未来更改。
在清单 3 中所示的代码版本中,我已经更改了内部实现以使用名称部件的关联数组。比较理想的情况是,我希望拥有错误处理并且更仔细地检查元素是否存在,但是本例的目 的在于展示使用我的类的代码无需更改的程度 — 代码并没有察觉到类发生更改。记住采用 OO 习惯的原因是要谨慎封装更改,这样代码将更具有可扩展性并且更容易维护。
2.做个好邻居
在构建类时,它应当正确地处理自己的错误。如果该类不知道如何处理错误,则应当以其调用者理解的格式封装这些错误。此外,避免返回空对象或者状态无效的对 象。许多时候,只需通过检验参数并抛出特定异常说明提供参数无效的原因就可以实现这一点。在您养成这个习惯时,它可以帮您 — 和维护代码或使用对象的人员 — 节省很多时间。
考虑清单 4 中所示的示例,该示例将接受一些参数并返回填充了一些值的 Person
对象。但是,在 parsePersonName()
方法中,没有验证提供的 $val
变量是否为空、是否是零长度字符串或者字符串是否使用无法解析的格式。parsePersonName()
方法不返回 Person
对象,但是返回 null。使用这种方法的管理员或程序员可能会觉得很麻烦 — 至少他们现在需要开始设置断点并调试 PHP 脚本。
好习惯:每个模块都处理自己的错误
最终目的是希望人们能够使用您的类,而不必了解其中的工作原理。如果他们使用的方法不正确或者不是按照期望的方法使用,也不需要猜测不能工作的原因。作为一个好邻居,您需要知道对您的类进行重用的人并没有特异功能,因此您需要解决猜测的问题。
3.避免看到美杜莎
在我最初了解 OO 概念时,我十分怀疑接口是否真正有帮助。我的同事给我打了个比方,说不使用接口就好像看到美杜莎的头。在希腊神话中,美杜莎是长着蛇发的女怪。凡是看了她 一眼的人都会变成石头。杀死美杜莎的珀尔休斯通过在盾上观察她的影子,避免了变成石头而得以与她对抗。
接口就是对付美杜莎的镜子。当您使用一个特定的具体实现时,代码也必须随着实现代码的更改而更改。直接使用实现将限制您的选择,因为您已经在本质上把类变成了 “石头”。
清单 6 显示了从数据库中装入 Person
对象的示例。它将获取人员的姓名并返回数据库中匹配的 Person
对象。
在环境发生更改之前,从数据库中装入 Person
的代码都可以正常运行。例如,从数据库装入 Person
可能适用于第一个版本的应用程序,但是对于第二个版本,可能需要添加从 Web 服务装入人员的功能。其实,该类已经变成 “石头”,因为它在直接使用实现类并且现在能做的更改十分有限
清单 7 显示了一个代码示例,在实现了加载用户的新方法后并没有进行更改。该示例显示了一个名为 PersonProvider
的接口,该接口将声明单个方法。如果任何代码使用 PersonProvider
,代码都禁止直接使用实现类。相反,它就像是一个实际对象一样使用 PersonProvider
。
在使用接口时,尝试避免直接引用实现类。相反,使用对象外部的内容可以提供正确的实现。如果您的类将装入基于某些逻辑的实现,它仍然需要获取所有实现类的定义,并且那样做也无法取得任何效果。
您可以使用 Factory 模式来创建实现接口的实现类的实例。根据约定,factory
方法将以 create
为开头并返回接口。它可以为您的 factory
获取必要的参数以计算出应当返回哪个实现类。
在清单 7 中,createProvider()
方法只是获取 $type
。如果 $type
被设为 database
,工厂将返回 DBPersonProvider
的实例。从数据库中装入人员的任何新实现都不要求在使用工厂和接口的类中进行任何更改。DBPersonProvider
将实现 PersonProvider
接口并且拥有 getPerson()
方法的实际实现。
将模块松散耦合 在一起是件好事情;它是允许您封装更改的属性之一。另外两个习惯 — “保持谨慎” 和 “避免看到美杜莎” — 可帮助您构建松散耦合的模块。要实现松散耦合的类,可通过养成降低类依赖关系的习惯实现。
在清单 8 中,降低依赖关系并不是必须降低使用对象的客户机的依赖关系。相反,该示例将演示如何降低与正确类的依赖关系并最小化这种依赖关系。
在Address
对象上调用format()
方法的代码可能看上去很棒 — 这段代码所做的是使用Address
类,调用format()
并完成。相反,Address
类就没那么幸运。它需要了解用于正确格式化的各种格式化方法,这可能使Address
对象无法被其他人很好地重用,尤其是在其他人没有兴趣在format()
方法中使用格式化方法类的情况下。虽然使用Address
的代码没有许多依赖关系,但是Address
类却有大量代码,而它可能只是一个简单的数据对象。Address
类与知道如何格式化Address
对象的实现类紧密耦合。
在构建优秀的 OO 设计时,必须考虑称为关注点分离(Separation of Concerns,SoC)的概念。SoC 指尝试通过真正关注的内容分离对象,从而降低耦合度。在最初的 Address
类中,它必须关注如何进行格式化。这可能不是优秀的设计。然而,Address
类应当考虑 Address
的各部分,而某种格式化方法应当关注如何正确格式化地址。
在清单 9 中,格式化地址的代码被移到接口、实现类和工厂中 — 养成 “使用接口” 的习惯。现在,AddressFormatUtils
类负责创建格式化方法并格式化 Address
。任何其他对象现在都可以使用 Address
而不必担心要求获得格式化方法的定义。
当然,缺点是只要使用模式,通常就意味着工件(类、文件)的数量会增加。但是,通过减少每个类中的维护可以弥补这个缺点,甚至在获得正确的可重用性时反而可以减少工件量。
具有高度内聚力的 OO 设计被集中并组织到相关模块中。了解 “关注点” 对于决定如何紧密地联系函数和类十分重要。
当设计的内聚力较低 时,它就不能良好地组织类和方法。意大利面条式代码(spaghetti code)一词通常用于描述捆绑在一起并且具有低内聚力的类和方法。清单 10 提供了意大利面条式代码的示例。相对通用的 Utils
类将使用许多不同对象并且有许多依赖关系。它执行很多操作,因而很难实现重用。
高内聚力 指将相互关联的类和方法分组在一起。如果方法和类都具有高度的内聚力,则可以轻松地分解整个组而不影响设计。具有高内聚力的设计将提供降低耦合的机会。清单 11 显示了被较好组织到类中的两个方法。AddressUtils
类将包含用于处理 Address
类的方法,显示了与地址相关的方法之间的高度内聚力。同样地,PersonUtils
将包含专门处理 Person
对象的方法。这两个拥有高度内聚力方法的新类的耦合性都很低,因为可以完全独立地使用。
清单 11. 高内聚力的好习惯
6.限制传播
OO 语言最大的敌人是复制和粘贴操作。当在缺少预先 OO 设计的情况下使用时,没有任何操作会像在类之间复制代码那样具有破坏性。无论何时,如果想将代码从一个类复制到下一个类中,请停下来并考虑如何使用类层次 结构利用类似功能或相同功能。在大多数情况下,使用优秀设计后,您将会发现完全没有必要复制代码。
清单 12 显示了部分类的简单示例。它们从重复的字段和方法开始 — 从长远来看,不利于应用程序作出更改。如果 Person
类中有缺陷,则 Employee
类中也很可能有一个缺陷,因为看上去似乎实现是在两个类之间复制的。
继承 是一个很难入手的习惯,因为构建正确继承模型的分析通常需要花费大量时间。反过来,使用 Ctrl+C 组合键和 Ctrl+V 组合键构建新实现只需几秒钟。但是省下的这部分时间通常会在维护阶段迅速抵销掉,因为应用程序实际上将花费大量进行维护。
在清单 13 中,新 Employee
类将扩展 Person
类。它现在将继承所有通用方法并且不重新实现这些方法。此外,清单 13 显示了抽象方法的用法,演示如何将基本功能放入基类中以及如何阻止实现类使用特定函数。
设计模式指对象和方法的常见交互,并且时间证明它可以解决某些问题。当您考虑使用设计模式时,您就需要了解类之间如何进行交互。它是构建类及其交互操作的简单方法,无需重蹈他人的覆辙,并从经过证明的设计中获益。
实际上没有适当的代码示例可以演示如何考虑使用模式(尽管有丰富的优秀示例可以显示模式实现)。但是,一般而言,您知道在满足以下条件时一次只能考虑一个对象:
- 不会提前设计对象模型。
- 开始编写单一方法的实现,而无需去掉大部分模型。
- 在交谈中不使用设计模式名而宁愿谈论实现。
一般而言,当您在执行以下操作时就是在考虑使用模式:
- 提前构建类及其交互操作。
- 根据模式套用类。
- 使用模式名,如 Factory、Singleton 和 Facade。
- 去掉大部分模型,然后开始添加实现。
原文出处:http://www.ibm.com/developerworks/cn/opensource/os-php-7oohabits/index.html