`

在 PHP 中养成 7 个面向对象的好习惯

阅读更多

在 PHP 编程早期,PHP 代码在本质上是限于面向过程的。过程代码 的特征在于使用过程构建应用程序块。过程通过允许过程之间的调用提供某种程度的重用。

但是,没有面向对象的语言构造,程序员仍然可以把 OO 特性引入到 PHP 代码中。这样做有点困难并且会使代码难于阅读,因为它是混合范例(含有伪 OO 设计的过程语言)。使用 PHP 代码中的 OO 构造 — 例如能够定义和使用类、能够构建使用继承的类之间的关系以及能够定义接口 — 可以更轻松地构建符合优秀 OO 实践的代码。

虽然没有过多模块化的纯过程设计运行得很好,但是 OO 设计的优点表现在维护上。由于典型应用程序的大部分生命周期都花费在维护上,因此代码维护是应用程序生命周期的重要部分。并且在开发过程中代码维护很容易 被遗忘。如果在应用程序开发和部署方面存在竞争,那么长期可维护性可能被放在比较次要的地位。

模块化 — 优秀 OO 设计的主要特性之一 — 可以帮助完成这样的维护。模块化将帮助封装更改,这样可以随着时间的推移更轻松地扩展和修改应用程序。

总的来说,虽然构建 OO 软件的习惯不止 7 个,但是遵循这里的 7 个习惯可以使代码符合基本 OO 设计标准。它们将为您提供更牢固的基础,在此基础之上建立更多 OO 习惯并构建可轻松维护与扩展的软件。这些习惯针对模块化的几个主要特性。有关独立于语言的 OO 设计优点的更多信息,请参阅 参考资料

7 个优秀 PHP OO 习惯包括:

  1. 保持谦虚。
  2. 做个好邻居。
  3. 避免看到美杜莎。
  4. 利用最弱的链接。
  5. 您是橡皮;我是胶水。
  6. 限制传播。
  7. 考虑使用模式。

保持谦虚

保持谦虚指避免在类实现和函数实现中暴露自己。隐藏您的信息是一项基本习惯。如果不能养成隐藏实现细节的习惯,那么将很难养成任何其他习惯。信息隐藏也称为封装

直接公开公共字段是一个坏习惯的原因有很多,最重要的原因是让您在实现更改中没有应有的选择。使用 OO 概念隔离更改,而封装在确保所作更改在本质上不是病毒性(viral)更改方面扮演不可或缺的角色。病毒性 更改是开始时很小的更改 — 如将保存三个元素的数组更改为一个只包含两个元素的数组。突然,您发现需要更改越来越多的代码以适应本应十分微不足道的更改。

开始隐藏信息的一种简单方法是保持字段私有并且用公共访问方法公开这些字段,就像家中的窗户一样。并没有让整面墙都朝外部开放,而只打开一两扇窗户(我将在 “好习惯:使用公共访问方法” 中介绍访问方法的更多信息)。

除了允许您的实现隐藏在更改之后外,使用公共访问方法而非直接公开字段将允许您在基本实现的基础上进行构建,方法为覆盖访问方法的实现以执行略微不同于父方法的行为。它还允许您构建一个抽象实现,从而使实际实现委托给覆盖基本实现的类。

坏习惯:公开公共字段

在清单 1 的坏代码示例中,Person 对象的字段被直接公开为公共字段而非使用访问方法。虽然此行为十分诱人,尤其对于轻量级数据对象来说更是如此,但是它将对您提出限制。


清单 1. 公开公共字段的坏习惯
<?php
class Person
{
public $prefix;
public $givenName;
public $familyName;
public $suffix;
}

$person = new Person();
$person->prefix = "Mr.";
$person->givenName = "John";

echo($person->prefix);
echo($person->givenName);

?>


如果对象有任何更改,则使用该对象的所有代码也都需要更改。例如,如果某人的教名、姓氏和其他名字被封装到 PersonName 对象中,则需要修改所有代码以适应更改。

好习惯:使用公共访问方法

通过使用优秀的 OO 习惯(参见清单 2),同一个对象现在拥有私有字段而非公共字段,并且通过称为访问方法  get  set 公共方法谨慎地向外界公开私有字段。这些访问方法现在提供了一种从 PHP 类中获取信息的公共方法,这样在实现发生更改时,更改使用类的所有代码的需求很可能变小。


清单 2. 使用公共访问方法的好习惯
<?php
class Person
{
private $prefix;
private $givenName;
private $familyName;
private $suffix;

public function setPrefix($prefix)
{
$this->prefix = $prefix;
}

public function getPrefix()
{
return $this->prefix;
}

public function setGivenName($gn)
{
$this->givenName = $gn;
}

public function getGivenName()
{
return $this->givenName;
}

public function setFamilyName($fn)
{
$this->familyName = $fn;
}

public function getFamilyName()
{
return $this->familyName;
}

public function setSuffix($suffix)
{
$this->suffix = $suffix;
}

public function getSuffix()
{
return $suffix;
}

}

$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");

echo($person->getPrefix());
echo($person->getGivenName());

?>


乍看之下,这段代码可能会完成大量工作,并且实际上可能更多是在前端的工作。但是,通常,使用优秀的 OO 习惯从长远来看十分划算,因为将极大地巩固未来更改。

在清单 3 中所示的代码版本中,我已经更改了内部实现以使用名称部件的关联数组。比较理想的情况是,我希望拥有错误处理并且更仔细地检查元素是否存在,但是本例的目 的在于展示使用我的类的代码无需更改的程度 — 代码并没有察觉到类发生更改。记住采用 OO 习惯的原因是要谨慎封装更改,这样代码将更具有可扩展性并且更容易维护。


清单 3. 使用不同内部实现的另一个示例
<?php
class Person
{
private $personName = array();

public function setPrefix($prefix)
{
$this->personName['prefix'] = $prefix;
}

public function getPrefix()
{
return $this->personName['prefix'];
}

public function setGivenName($gn)
{
$this->personName['givenName'] = $gn;
}

public function getGivenName()
{
return $this->personName['givenName'];
}

/* etc... */
}

/*
* Even though the internal implementation changed, the code here stays exactly
* the same. The change has been encapsulated only to the Person class.
*/
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");

echo($person->getPrefix());
echo($person->getGivenName());

?>








做个好邻居

在构建类时,它应当正确地处理自己的错误。如果该类不知道如何处理错误,则应当以其调用者理解的格式封装这些错误。此外,避免返回空对象或者状态无 效的对象。许多时候,只需通过检验参数并抛出特定异常说明提供参数无效的原因就可以实现这一点。在您养成这个习惯时,它可以帮您 — 和维护代码或使用对象的人员 — 节省很多时间。

坏习惯:不处理错误

考虑清单 4 中所示的示例,该示例将接受一些参数并返回填充了一些值的 Person 对象。但是,在parsePersonName() 方法中,没有验证提供的 $val 变量是否为空、是否是零长度字符串或者字符串是否使用无法解析的格式。parsePersonName() 方法不返回 Person 对象,但是返回 null。使用这种方法的管理员或程序员可能会觉得很麻烦 — 至少他们现在需要开始设置断点并调试 PHP 脚本。


清单 4. 不抛出或处理错误的坏习惯
class PersonUtils
{
public static function parsePersonName($format, $val)
{
if (strpos(",", $val) > 0) {
$person = new Person();
$parts = split(",", $val); // Assume the value is last, first
$person->setGivenName($parts[1]);
$person->setFamilyName($parts[0]);
}
return $person;
}
}


清单 4 中的 parsePersonName() 方法可以修改为在 if 条件外部初始化 Person 对象,确保总是获得有效的 Person 对象。但是,您得到的是没有 set 属性的 Person,这仍然没有很好地改善您的困境。

好习惯:每个模块都处理自己的错误

不要让调用方凭空猜测,而是对参数进行预先验证。如果未设置的变量无法生成有效的结果,请检查变量并抛出 InvalidArgumentException。如果字符串不能为空或者必须为特定格式,请检查格式并抛出异常。清单 5 解释了如何在演示一些基本验证的 parsePerson() 方法中创建异常以及一些新条件。


清单 5. 抛出错误的好习惯
<?php
class InvalidPersonNameFormatException extends LogicException {}


class PersonUtils
{
public static function parsePersonName($format, $val)
{
if (! $format) {
throw new InvalidPersonNameFormatException("Invalid PersonName format.");
}

if ((! isset($val)) || strlen($val) == 0) {
throw new InvalidArgumentException("Must supply a non-null value to parse.");
}


}
}
?>


最终目的是希望人们能够使用您的类,而不必了解其中的工作原理。如果他们使用的方法不正确或者不是按照期望的方法使用,也不需要猜测不能工作的原因。作为一个好邻居,您需要知道对您的类进行重用的人并没有特异功能,因此您需要解决猜测的问题。







避免看到美杜莎

在我最初了解 OO 概念时,我十分怀疑接口是否真正有帮助。我的同事给我打了个比方,说不使用接口就好像看到美杜莎的头。在希腊神话中,美杜莎是长着蛇发的女怪。凡是看了她 一眼的人都会变成石头。杀死美杜莎的珀尔休斯通过在盾上观察她的影子,避免了变成石头而得以与她对抗。

接口就是对付美杜莎的镜子。当您使用一个特定的具体实现时,代码也必须随着实现代码的更改而更改。直接使用实现将限制您的选择,因为您已经在本质上把类变成了 “石头”。

坏习惯:不使用接口

清单 6 显示了从数据库中装入 Person 对象的示例。它将获取人员的姓名并返回数据库中匹配的Person 对象。


清单 6. 不使用接口的坏习惯
<?php
class DBPersonProvider
{
public function getPerson($givenName, $familyName)
{
/* go to the database, get the person... */
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
return $person;
}
}

/* I need to get person data... */
$provider = new DBPersonProvider();
$person = $provider->getPerson("John", "Doe");

echo($person->getPrefix());
echo($person->getGivenName());

?>


在环境发生更改之前,从数据库中装入 Person 的代码都可以正常运行。例如,从数据库装入 Person可能适用于第一个版本的应用程序,但是对于第二个版本,可能需要添加从 Web 服务装入人员的功能。其实,该类已经变成 “石头”,因为它在直接使用实现类并且现在能做的更改十分有限。

好习惯:使用接口

清单 7 显示了一个代码示例,在实现了加载用户的新方法后并没有进行更改。该示例显示了一个名为PersonProvider 的接口,该接口将声明单个方法。如果任何代码使用 PersonProvider,代码都禁止直接使用实现类。相反,它就像是一个实际对象一样使用 PersonProvider


清单 7. 使用接口的好习惯
<?php
interface PersonProvider
{
public function getPerson($givenName, $familyName);
}

class DBPersonProvider implements PersonProvider
{
public function getPerson($givenName, $familyName)
{
/* pretend to go to the database, get the person... */
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
return $person;
}
}

class PersonProviderFactory
{
public static function createProvider($type)
{
if ($type == 'database')
{
return new DBPersonProvider();
} else {
return new NullProvider();
}
}
}

$config = 'database';
/* I need to get person data... */
$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";

class Address
{
private $addressLine1;
private $addressLine2;
private $city;
private $state; // or province...
private $postalCode;
private $country;

public function setAddressLine1($line1)
{
$this->addressLine1 = $line1;
}

/* accessors, etc... */

public function getCountry()
{
return $this->country;
}

public function format($type)
{
if ($type == "inline") {
$formatter = new InlineAddressFormatter();
} else if ($type == "multiline") {
$formatter = new MultilineAddressFormatter();
} else {
$formatter = new NullAddressFormatter();
}
return $formatter->format($this->getAddressLine1(),
$this->getAddressLine2(),
$this->getCity(), $this->getState(), $this->getPostalCode(),
$this->getCountry());
}
}

$addr = new Address();
$addr->setAddressLine1("123 Any St.");
$addr->setAddressLine2("Ste 200");
$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 设计时,必须考虑称为关注点分离(Separation of Concerns,SoC)的概念。SoC 指尝试通过真正关注的内容分离对象,从而降低耦合度。在最初的 Address 类中,它必须关注如何进行格式化。这可能不是优秀的设计。然而,Address 类应当考虑 Address 的各部分,而某种格式化方法应当关注如何正确格式化地址。

在清单 9 中,格式化地址的代码被移到接口、实现类和工厂中 — 养成 “使用接口” 的习惯。现在,AddressFormatUtils 类负责创建格式化方法并格式化 Address。任何其他对象现在都可以使用Address 而不必担心要求获得格式化方法的定义。

 

分享到:
评论

相关推荐

    在PHP中养成7个面向对象的好习惯

    以下是在PHP中养成七个面向对象好习惯的具体解释: 1. **保持谦虚**:这个习惯指的是避免在类中直接暴露实现细节,即信息隐藏或封装。通过将类的属性设为私有(private),并通过公共访问方法(getter和setter)来...

    一个php面向对象数据库操作类库.zip

    一个php面向对象数据库操作类库,注意使用时最后不要忘记关闭数据路连接$SqlDB-&gt;Close();当然这句可以不要,php会自动注销!但是这样能够养成一个好的习惯,最好还是加上!

    PHP 编程好习惯,值得学习

    ### PHP编程好习惯详解 #### 引言 在软件开发领域,尤其是PHP编程中,优秀的编程习惯对于提高代码质量和效率至关重要。本篇文章旨在探讨并详细解释五个有助于提升PHP程序员技能的关键编程习惯,帮助读者理解如何...

    php 基础简单练习demo应用

    10. **面向对象编程**:PHP5引入了面向对象的概念,支持类、对象、继承、封装和多态。例如: ```php class Person { public $name; function greet() { echo "你好, " . $this-&gt;name; } } $p = new Person(); $p...

    php编程,php中文手册

    PHP(Hypertext Preprocessor,超文本预处理器)是一种广泛使用的开源服务器端脚本语言,尤其适用于Web开发,...无论是初学者还是经验丰富的开发者,都应该养成经常查阅手册的好习惯,以便于不断提升自己的技能水平。

    PHP.Web.2.0开发实战 随书源码chapter-02

    9. **面向对象编程**:PHP 5引入了完整的面向对象编程支持,包括类、对象、属性、方法、继承、封装和多态等概念。 10. **数据库交互**:学习如何使用PHP连接和操作MySQL数据库,包括PDO(PHP Data Objects)或...

    php新手上路中文教程php新手上路中文教程

    9. **面向对象编程**:介绍类的定义、对象的创建、继承、封装和多态性等OOP概念。 10. **数据库交互**:讲解如何使用PHP连接和操作MySQL数据库,进行数据查询、插入、更新和删除操作。 11. **PHP与HTML的结合**:...

    《PHP精解案例教程》代码

    7. **面向对象编程**:PHP5引入了完整的面向对象特性,包括类、对象、继承、封装、多态等概念,是进阶学习的重要部分。 8. ** Sessions 和 Cookies**:了解如何使用session和cookie进行用户状态管理,提供跨页面的...

    PHP经典实例第二版 pdf

    在面向对象编程方面,本书不仅讲解了OOP的基本概念,如类的创建、继承、封装和多态性,还详细介绍了如何在PHP项目中应用这些概念。为了进一步提高代码的质量和可维护性,本书还涉及了模板引擎的使用、设计模式的介绍...

    PHP程序设计-3期(KC016) 常见问题2-1 PHP语法规则.docx

    因此,养成良好的编码习惯,如在文件末尾加上`?&gt;`,可以避免这类潜在问题。 在编写PHP代码时,还有一些其他的语法规则需要注意: 1. **变量声明**:PHP使用`$`符号来标识变量,例如`$name = "John";`。变量不需要...

    PHP+Python+JDK+Android+Apache+C++各种开发文档和手册

    在IT行业中,编程语言的学习和应用离不开详细的文档和手册,因为它们是开发者的重要参考资料。"PHP+Python+JDK+Android+Apache+C++各种开发文档...因此,将这些文档整理并随时查阅,是每一个IT从业者都应该养成的习惯。

    如何让PHP编码更加好看利于阅读

    写出优秀的程序代码是一门艺术,要想如此,就必须在一开始就养成良好的编程习惯。良好的编程习惯不仅有助于项目初期的设计(如模块化),还可以使你编写的代码更易于理解,从而使代码的维护工作更轻松、更省力。不好...

    WebProPHP:WebProPHP

    4. **PHP面向对象编程**:随着版本更新,PHP支持了完整的面向对象编程特性,包括类、对象、继承、封装和多态。理解这些概念有助于编写可维护、可扩展的代码。 5. **错误处理和调试**:学会使用错误报告、日志记录和...

    Java心得 学JAVA必看

    这包括但不限于Java基础语法、面向对象编程思想、集合框架、多线程编程、异常处理等内容。只有建立了完整的知识框架,才能更好地理解和运用Java语言。 #### 3. 实践出真知 理论学习固然重要,但更重要的是实践。...

    thinkPHP__框架

    同时,它支持面向对象编程(OOP),帮助开发者养成良好的编程习惯。 3. 高效性能:框架内建了缓存机制和优化策略,如路由优化、SQL查询优化等,能有效提高应用程序的运行速度。 4. 安全性:ThinkPHP内置了防止SQL...

    kb:知识库说明

    3. **最佳实践**:知识库应当包含PHP的最佳实践,如避免魔术引号、合理使用命名空间、控制循环性能等,以帮助开发者养成良好的编程习惯。 4. **错误与异常处理**:详述PHP的错误报告和异常处理机制,包括错误级别、...

Global site tag (gtag.js) - Google Analytics