早在 2010 年,MailChimp 在他们的博客上发表了一篇文章,题为EWW,你使用 PHP 吗?在这篇博客文章中,他们描述了他们对 PHP 的选择时的恐惧,他们认为这个短语是好的 PHP 程序员 To3T3。在他们的反驳中,他们辩称他们的 PHP 不是你祖父的 PHP,他们使用的是一个复杂的框架。我倾向于根据 PHP 的功能、安全性和架构来判断 PHP 的质量。这本书重点介绍如何构建代码。软件的设计允许开发人员以一种无 bug 且优雅的方式轻松地扩展代码,使其超出原始目的。
正如马丁·福勒所说:
“任何傻瓜都能写出计算机能理解的代码。优秀的程序员能写出人类能理解的代码。”
这不仅限于代码样式,还包括开发人员如何设计和构造代码。我遇到过许多开发人员,他们的鼻子总是卡在文档中,复制和粘贴代码,直到它工作为止;将代码片段拼凑在一起,直到成功。此外,我经常看到软件开发过程迅速恶化,因为开发人员将他们的类与长度不断增加的函数更加紧密地结合在一起。
软件工程师不能只编写软件代码;他们必须知道如何设计它。事实上,一个好的软件工程师在采访其他软件工程师时,常常会问一些关于代码本身设计的问题。获取一段将要执行的代码是很简单的,询问开发人员strtolower
或str2lower
是否是函数的正确名称(记录在案,它是strtolower
)也是很好的。知道类和对象之间的区别并不能使你成为一个合格的开发人员;例如,一个更好的面试问题是如何将子类型多态性应用于真正的软件开发挑战。未能评估软件设计技能会使面试变得枯燥无味,导致无法区分擅长软件设计的人和不擅长软件设计的人。这些高级主题将在本书中讨论,通过学习这些策略,您将更好地理解在讨论软件体系结构时应该问哪些问题。
莫西·马林斯派克曾在推特上发过以下消息:
“作为一名软件开发人员,我羡慕作家、音乐家和电影制作人。与软件不同,当他们创造出某种东西时,它真的会永远完成。”。
在开发软件时,我们不能忘记我们是作者,不仅仅是机器指令的作者,我们还创作了一些我们以后希望其他人能够扩展的东西。因此,我们的代码不能只针对机器,还必须针对人类。代码不仅仅是机器的诗,它也应该是人类的诗。
当然,这说得比做得好。在 PHP 中,考虑到 PHP 为开发人员提供了构建和构造代码的自由,这可能会特别困难。由于自由的本质,它可能被使用和滥用,因此 PHP 提供的自由也是如此。
因此,开发人员了解适当的软件设计实践以确保其代码保持长期可维护性变得越来越重要。事实上,另一项关键技能在于重构代码,改进现有代码的设计,使其更易于长期扩展。
技术债务是糟糕的系统设计的最终结果,是我在 PHP 开发人员的职业生涯中发现的。无论是处理提供高级功能的系统还是简单网站,这对我来说都是真实的。这通常是因为开发人员出于各种原因选择实施糟糕的设计;这是在向现有代码库添加功能或在软件的初始构建过程中做出糟糕的设计决策时发生的。重构可以帮助我们解决这些问题。
SensioLabs(Symfony 框架的创建者)有一个名为Insight的工具,允许开发人员在自己的代码中计算技术债务。2011 年,他们使用该工具对各种项目的技术债务进行了评估;不出所料,他们发现 WordPress 4.1 在他们评估的所有平台中高居榜首,他们声称需要 20.1 年才能解决项目所包含的技术债务。
那些熟悉 WordPress 核心的人可能不会对此感到惊讶,但这个问题当然不仅仅与 WordPress 有关。在我使用 PHP 的职业生涯中,从使用安全关键型加密系统到使用任务关键型嵌入式系统,处理技术债务都是我的工作。对于一个 PHP 开发者来说,处理技术债务并不是一件值得羞愧的事,事实上有些人可能认为它是勇敢的。处理技术债务绝非易事,尤其是面对越来越苛刻的用户群、客户或项目经理;不断要求更多的功能,而不熟悉与项目相关的技术债务。
我最近发电子邮件给 PHP 内部组,关于他们是否应该考虑贬低错误抑制操作员当任何 PHP 函数前面有@符号时,该函数将抑制它返回的错误。这可能很残酷,尤其是当该函数出现致命错误时,该错误会停止脚本的执行,从而使调试成为一项艰巨的任务。如果错误被抑制,脚本可能无法执行,而不向开发人员提供导致错误的原因。在某些情况下,此运算符的使用可能被描述为反模式,我们将在第 4 章、结构设计模式中介绍。
尽管没有人反对有比滥用错误抑制操作符更好的处理错误的方法(try
/catch
、proper validation
),并且 PHP 的最终目标应该是弃用,但有些函数返回不必要的警告,即使它们已经具有成功/失败值。这意味着,由于 PHP 核心本身的技术债务,在完成大量其他先决条件工作之前,不能不推荐使用该操作符。同时,由开发人员决定处理错误的最佳方法。在解决不必要的错误报告的固有问题之前,不能弃用此运算符。因此,应该教育开发人员如何使用适当的方法来处理错误,而不是经常使用@
符号。
从根本上说,技术债务会减慢项目的开发速度,并经常导致代码被部署,而当开发人员尝试处理脆弱的项目时,代码被破坏。
当开始一个新项目时,不要害怕讨论架构,因为架构会议对于开发人员协作至关重要;正如一位与我共事的 Scrum 大师在面对批评时所说的那样,“会议是工作的绝佳替代品”,他说“会议就是工作……没有会议,你会做多少工作?”。
在本章的其余部分中,我们将介绍以下几点:
说到编码风格,我想向您介绍由 PHP 框架互操作组创建的 PSR 标准。即,适用于编码标准的两个标准是 PSR-1(基本编码样式)和 PSR-2(编码样式指南)。除此之外,还有涵盖其他领域的 PSR 标准,例如,截至今天;PSR-4 标准是集团发布的最新自动加载标准。有关标准的更多信息,请访问http://www.php-fig.org/ 。
用于在整个代码库中实现一致性的编码风格是我坚信的。在整个项目中,它确实会对代码的可读性产生影响。当您开始一个项目时,这一点尤其重要(您可能正在阅读这本书,以了解如何正确地完成这一工作),因为您的编码风格决定了开发人员在处理该项目时将采用的风格。使用全球标准,如 PSR-1 或 PSR-2,意味着开发人员可以轻松地在项目之间切换,而无需在 IDE 中重新配置代码样式。好的代码样式可以使格式错误更容易发现。不用说,编码风格将随着时间的推移而发展,到目前为止,我选择使用 PSR 标准。
我非常相信这句话:一个永远都是密码,好像最终维护你密码的人将是一个知道你住在哪里的暴力精神病患者。不知道这句话是谁写的,但人们普遍认为可能是约翰·伍兹或潜在的马丁·戈尔丁。
我强烈建议您在阅读本书之前熟悉这些标准。
面向对象编程不仅仅是类和对象;这是一个基于包含数据字段和方法的对象(数据结构)的完整编程范例。必须理解这一点;使用类将一堆不相关的方法组织在一起不是面向对象的。
假设您知道类(以及如何实例化它们),请允许我提醒您一些不同的细节。
多态性对于一个相当简单的概念来说是一个相当长的词。本质上,多态性意味着相同的接口与不同的底层代码一起使用。因此,多个类可以有一个 draw 函数,每个类都接受相同的参数,但在底层,代码的实现方式不同。
在本节中,我将特别讨论子类型多态性(也称为子类型或包含多态性)。
让我们假设动物是我们的超型;我们的亚型可能是猫、狗和羊。
在 PHP 中,接口允许您定义实现它的类必须包含的一组功能,从 PHP7 开始,您还可以使用标量类型提示来定义我们期望的返回类型。
例如,假设我们定义了以下接口:
interface Animal
{
public function eat(string $food) : bool;
public function talk(bool $shout) : string;
}
然后,我们可以在自己的类中实现此接口,如下所示:
class Cat implements Animal {
}
如果我们在不定义类的情况下运行此代码,我们将得到如下错误消息:
Class Cat contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Animal::eat, Animal::talk)
本质上,我们需要实现我们在接口中定义的方法,所以现在让我们继续创建一个实现这些方法的类:
class Cat implements Animal
{
public function eat(string $food): bool
{
if ($food === "tuna") {
return true;
} else {
return false;
}
}
public function talk(bool $shout): string
{
if ($shout === true) {
return "MEOW!";
} else {
return "Meow.";
}
}
}
既然我们已经实现了这些方法,那么我们就可以实例化我们所关注的类,并使用其中包含的函数:
$felix = new Cat();echo
$felix->talk(false);
那么多态性从何而来?假设我们有另一门狗课:
class Dog implements Animal
{
public function eat(string $food): bool
{
if (($food === "dog food") || ($food === "meat")) {
return true;
} else {
return false;
}
}
public function talk(bool $shout): string
{
if ($shout === true) {
return "WOOF!";
} else {
return "Woof woof.";
}
}
}
现在让我们假设在一个pets
数组中有多种不同类型的动物:
$pets = array(
'felix' => new Cat(),
'oscar' => new Dog(),
'snowflake' => new Cat()
);
为了运行talk
功能,我们现在可以单独遍历所有这些宠物。我们不关心 pet 的类型,因为在我们得到的每个类中实现的talk
方法是由于我们扩展了动物接口。
所以让我们假设我们想让所有的动物都运行talk
方法。我们可以使用以下代码:
foreach ($pets as $pet) {
echo $pet->talk(false);
}
不需要不必要的switch
/case
块来包装我们的类,我们只是使用软件设计,从长远来看让事情变得更容易。
抽象类以类似的方式工作,除了抽象类可以包含接口不能包含的功能。
需要注意的是,定义一个或多个抽象类的任何类也必须定义为抽象类。不能使用普通类定义抽象方法,但可以在抽象类中使用普通方法。让我们首先将接口重构为一个抽象类:
abstract class Animal
{
abstract public function eat(string $food) : bool;
abstract public function talk(bool $shout) : string;
public function walk(int $speed): bool {
if ($speed > 0) {
return true;
} else {
return false;
}
}
}
您可能已经注意到,我还添加了一个walk
方法作为普通的、非抽象的方法;这是一个标准方法,可以由继承父抽象类的任何类使用或扩展。它们已经有了自己的实现。
注意,不可能实例化抽象类(很像不可能实例化接口)。相反,我们必须扩大它。
因此,在我们的Cat
课程中,让我们删除以下内容:
class Cat implements Animal
我们将用以下代码替换它:
class Cat extends Animal
为了让类扩展Animal
抽象类,我们需要重构的就是这些。我们必须在类中实现我们为接口概述的抽象函数,此外,我们可以使用普通函数而无需实现它们:
$whiskers = new Cat();
$whiskers->walk(1);
从 PHP5.4 开始,在一个系统中实例化一个类并访问它的属性也成为可能。PHP.net 将其宣传为:已添加实例化时的类成员访问权限,例如:(new Foo)>bar()。您也可以使用单个属性来执行此操作,例如,(new Cat)->legs
。在我们的示例中,我们可以按如下方式使用它:
(new \IcyApril\ChapterOne\Cat())->walk(1);
简单回顾一下 PHP 如何实现 OOP 的其他几点,类声明或函数声明之前的final
关键字意味着您无法在定义此类类或函数后重写它们。
那么,让我们尝试扩展一个名为final
的类:
final class Animal
{
public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
}
这将产生以下输出:
Fatal error: Class Cat may not inherit from final class (Animal)
类似地,让我们在功能级别执行相同的操作:
class Animal
{
final public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
public function walk () {
return "walking with tail wagging...";
}
}
这将产生以下输出:
Fatal error: Cannot override final method Animal::walk()
特性被引入 PHP,作为引入水平重用的机制。PHP 通常充当单一继承语言,因为不能将多个类继承到脚本中。
传统的多重继承是一个有争议的过程,经常被软件工程师看不起。
让我给你举一个直接使用特质的例子;让我们定义一个抽象的Animal
类,并将其扩展到另一个类中:
class Animal
{
public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
public function walk () {
return "walking with tail wagging...";
}
}
现在让我们假设我们有一个函数来命名我们的类,但我们不希望它应用于扩展Animal
类的所有类,我们希望它应用于某些类,不管它们是否继承抽象Animal
类的属性。
我们定义了如下函数:
function setFirstName(string $name): bool
{
$this->firstName = $name;
return true;
}
function setLastName(string $name): bool
{
$this->lastName = $name;
return true;
}
现在的问题是,除了复制和粘贴不同的代码位或使用条件继承之外,如果不使用水平重用,我们将无法放置它们。这就是性格拯救的地方;让我们首先将这些方法包装在一个名为Name
的特征中:
trait Name
{
function setFirstName(string $name): bool
{
$this->firstName = $name;
return true;
}
function setLastName(string $name): bool
{
$this->lastName = $name;
return true;
}
}
现在我们已经定义了 Trait,我们可以告诉 PHP 在我们的Cat
类中使用它:
class Cat extends Animal
{
use Name;
public function walk()
{
return "walking with tail wagging...";
}
}
注意到Name
语句的用法了吗?这就是魔法发生的地方。现在,您可以毫无问题地调用该 Trait 中的函数:
$whiskers = new Cat();
$whiskers->setFirstName('Paul');
echo $whiskers->firstName;
总而言之,新代码块如下所示:
trait Name
{
function setFirstName(string $name): bool
{
$this->firstName = $name;
return true;
}
function setLastName(string $name): bool
{
$this->lastName = $name;
return true;
}
}
class Animal
{
public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
use Name;
public function walk()
{
return "walking with tail wagging...";
}
}
$whiskers = new Cat();
$whiskers->setFirstName('Paul');
echo $whiskers->firstName;
让我借此机会向您介绍一个名为标量类型暗示的 PHP7 概念;它允许您定义返回类型(是的,我知道这不严格属于 OOP 的范围;请处理它)。
让我们定义一个函数,如下所示:
function addNumbers (int $a, int $b): int
{
return $a + $b;
}
让我们来看看这个函数;首先,您会注意到,在每个参数之前,我们定义了希望接收的变量类型;在本例中,它是 int(或 integer)。接下来,您会注意到函数定义: int
后面有一段代码,它定义了我们的返回类型,因此我们的函数只能接收一个整数。
如果没有提供正确类型的变量作为函数参数,或者没有从函数返回正确类型的变量;你会得到一个TypeError
例外。在严格模式下,如果启用了严格模式,并且您提供的参数数量不正确,PHP 还会抛出一个TypeError
异常。
在 PHP 中也可以定义strict_types
;让我解释一下你为什么要这么做。如果没有strict_types
,PHP 将尝试在非常有限的情况下自动将变量转换为定义的类型。例如,如果您传递一个只包含数字的字符串,它将转换为整数,但非数字的字符串将导致TypeError
异常。一旦你启用strict_types
这一切改变,你就不能再拥有这种自动施法行为。
以我们前面的示例为例,在没有strict_types
的情况下,您可以执行以下操作:
echo addNumbers(5, "5.0");
启用strict_types
后再次尝试,您会发现 PHP 抛出TypeError
异常。
此配置仅适用于单个文件,将其放在包含其他文件之前不会导致将此配置继承到这些文件。为什么 PHP 选择走这条路有很多好处;它们在 RFC 的 0.5.3 版本中列出得非常清楚,该版本实现了称为PHP RFC:scalar type Declarations的标量类型提示。您可以通过阅读相关信息 http://www.wiki.php.net (维基,而非主要 PHP 网站)并搜索scalar_type_hints_v5
。
为了启用它,请确保将此作为 PHP 脚本中的第一条语句:
declare(strict_types=1);
除非您将strict_types
定义为 PHP 脚本中的第一条语句,否则这将不起作用;本定义不允许有其他用途。事实上,如果您稍后尝试定义它,您的脚本 PHP 将抛出一个致命错误。
当然,为了让愤怒的 PHP 核心狂热者阅读这本咖啡色的书,我应该提到,还有其他有效类型可以用于类型暗示。例如,PHP5.1.0 引入了数组,PHP5.0.0 引入了开发人员使用自己的类来实现这一点的能力。
假设我们有一个Address
课程,让我给你们一个简单的例子,说明这在实践中是如何起作用的:
class Address
{
public $firstLine;
public $postcode;
public $country;
public function __construct(string $firstLine, string $postcode, string $country)
{
$this->firstLine = $firstLine;
$this->postcode = $postcode;
$this->country = $country;
}
}
然后我们可以输入我们注入到Customer
类中的Address
类的提示:
class Customer
{
public $name;
public $address;
public function __construct($name, Address $address)
{
$this->name = $name;
$this->address = $address;
}
}
这就是它们如何结合在一起:
$address = new Address('10 Downing Street', 'SW1A 2AA', 'UK');
$customer = new Customer('Davey Cameron', $address);
var_dump($customer);
如果你定义了一个包含私有变量或受保护变量的类,你会注意到一个奇怪的行为,如果你要var_dump
这个类的对象。您会注意到,当您将对象包装在var_dump
中时,它会显示所有变量;无论是受保护的、私人的还是公共的。
PHP 将var_dump
视为一个内部调试函数,这意味着所有数据都变得可见。
幸运的是,有一个解决办法。PHP5.6 引入了__debugInfo
魔术方法。前面带有双下划线的类中的函数表示神奇的方法,并具有与之相关联的特殊功能。每次尝试var_dump
一个设置了__debugInfo
魔术方法的对象时,var_dump
将被该函数调用的结果覆盖。
让我向您展示这在实践中是如何工作的,让我们从定义一个类开始:
class Bear {
private $hasPaws = true;
}
让我们实例化这个类:
$richard = new Bear();
现在,如果我们尝试访问私有变量hasPaws
,我们将得到一个致命错误:
echo $richard->hasPaws;
前面的调用将导致引发以下致命错误:
Fatal error: Cannot access private property Bear::$hasPaws
这是预期的输出,我们不希望private
属性在其对象外部可见。也就是说,如果我们用一个var_dump
包裹对象,如下所示:
var_dump($richard);
然后我们将得到以下输出:
object(Bear)#1 (1) {
["hasPaws":"Bear":private]=>
bool(true)
}
如您所见,我们的private
属性标记为private
,但它仍然可见。那么,我们将如何预防这种情况呢?
那么,让我们重新定义我们的类,如下所示:
class Bear {
private $hasPaws = true;
public function __debugInfo () {
return call_user_func('get_object_vars', $this);
}
}
现在,在实例化我们的类和var_dump
结果对象之后,我们得到以下输出:
object(Bear)#1 (0) {
}
所有脚本现在看起来都是这样的,您会注意到我添加了一个额外的名为growls
的public
属性,我将其设置为true
:
<?php
class Bear {
private $hasPaws = true;
public $growls = true;
public function __debugInfo () {
return call_user_func('get_object_vars', $this);
}
}
$richard = new Bear();
var_dump($richard);
如果我们使用var_dump
这个脚本(同时使用public
和private
属性),我们将得到以下输出:
object(Bear)#1 (1) {
["growls"]=>
bool(true)
}
如您所见,只有public
属性可见。那么这个小实验的故事有什么寓意呢?首先,var_dumps
公开了对象内部的私有和受保护的属性,其次,这种行为可以被覆盖。
Composer是 PHP 的依赖项管理器,其灵感来源于 Node 的 NPM 和 Bundler。它现在已经成为多个 PHP 项目的一部分,包括 Laravel 和 Symfony。然而,它之所以对我们有用,是因为它包含符合 PSR-0 和 PSR-4 标准的自动加载功能。您可以从下载并安装 Composerhttp://getcomposer.org 。
要在 Mac OS X 或 Linux 上全局安装 Composer,首先可以运行安装程序:
**curl -sS https://getcomposer.org/installer | php**
然后,您可以移动 Composer 以全局安装它:
**mv composer.phar /usr/local/bin/composer**
如果前面的命令由于权限问题而失败,请重新运行该命令,但将sudo
放在最开始的位置除外。键入命令后,系统会要求您输入密码,只需输入密码并点击输入。
按照前面的步骤安装 Composer 后,只需运行composer
命令即可运行它。
要在 Windows 上安装 Composer,最简单的方法是在 Composer 网站上运行安装程序;目前,您可以在以下网址找到它:
https://getcomposer.org/Composer-Setup.exe 。
Composer 很容易更新,只需运行以下命令:
**Composer self-update**
Composer 通过使用名为composer.json
的文件中的配置来工作,您可以在其中概述外部依赖项和自动加载样式。一旦 Composer 安装了此文件中列出的依赖项,它就会编写一个composer.lock
文件,详细说明它安装的确切版本。使用版本控制时,提交此文件(与composer.json
文件一起提交)非常重要,不要将其添加到应用程序中。gitignore
如果您在 Git 上,请提交文件。这一点非常重要,因为锁文件详细说明了在版本控制系统中特定时间安装的软件包的确切版本。但是,您可以排除名为vendor
的目录,稍后我将解释它的作用。
让我们首先在项目目录中创建一个名为composer.json
的文件。这个文件是用 JSON 构建的,所以让我提醒您 JSON 是如何工作的:
"key" : "value"
\
是逃生钥匙现在我们可以在composer.json
文件中添加以下标记:
{
"autoload": {
"psr-4": {
"IcyApril\\ChapterOne": "src/"
}
}
}
让我解释一下这个文件的作用;它告诉 Composer 使用 PSR-4 标准将src/
目录中的所有内容autoload
放入IcyApril\ChapterOne
名称空间。
因此,下一步是创建我们的src
目录,其中包含我们想要自动加载的代码。那样做了吗?好的,现在让我们打开命令行,进入我们放置composer.json
文件的目录。
为了在项目中安装composer.json
文件中的所有内容,只需运行composer install
命令。对于后续更新,composer update
命令将更新为composer.json
中定义的所有依赖项的最新版本。不过,如果你不想这样做,还有另一种选择;运行composer dump-autoload
命令将仅重新生成需要包含在项目中的 PSR-0/PSR-4 类的列表(例如,添加、删除或重命名某些类)。
现在让我介绍一下如何实际创建一个类。因此,让我们在项目中创建一个src
目录,并在该src
目录中创建一个名为Book
的新类。您可以通过创建一个名为Book.php
的文件来实现这一点。在该文件中,添加如下内容:
<?php
namespace IcyApril\ChapterOne;
class Book
{
public function __construct()
{
echo "Hello world!";
}
}
这是一个标准类,但我们只是定义了一个构造函数,当类被实例化时,它将响应Hello world!
。
正如您可能已经注意到的,我们遵循了一些命名约定;首先,PSR-1 标准声明必须在 StudlyCaps 中声明类名。PSR-2 有一些额外的要求;举几个例子:四个空格而不是制表符,命名空间或 use 声明后一个空格,并在新行上放上括号。如果您还没有阅读这些标准,那么花时间阅读这些标准绝对值得。您可能不同意每一个标准,您可能对如何格式化自己的代码有主观偏好;我的建议是为了更大的利益把这些偏好放在一边。当在公共代码基础上进行协作时,通过使用 PSR 标准使代码标准化提供了巨大的优势。拥有由 PHP-FIG 集团等组织构建的外部标准的好处是,您可以将配置预先构建到 IDE 中(例如,PHPStorm 支持现成的 PSR-1/PSR-2)。不仅如此,当涉及到格式化参数时,您有一个具体的、公正的文档,该文档概述了应该如何操作,这对于在代码审查期间停止代码格式化参数非常有用。
现在我们已经创建了这个类,我们可以继续运行composer dump-autoload
命令来刷新我们的 autoloader 脚本。
因此,我们已经配置了 Composer autoloader,我们还可以使用一个测试类,但下一个问题是如何实现它。那么,让我们继续实施这个。在实现composer.json
文件的同一目录中,我们添加index.php
文件。
在您输入 PHP 开始标记后,我们需要输入 autoloader 脚本:
require_once('vendor/autoload.php');
然后我们可以实例化我们的Book
类:
new \IcyApril\ChapterOne\Book();
设置您的 web 服务器,将您的文档根指向我们创建的文件夹,将您的 web 浏览器指向您选择的 web 服务器,您将看到 Hello world!在屏幕上弹出。现在,您可以分解代码并对其进行处理。
本书附带了完整的代码示例,因此您可以打开它并直接在那里使用它,以防需要调试代码的帮助。
无论您的类是抽象类还是仅仅是接口,在自动加载时,我们都将它们视为类。
架构师克里斯托弗·亚历山大(Christopher Alexander)提到了如何使用模式来解决常见的设计问题,他最初记录了这个概念。这个想法来自亚历山大;他提出,设计问题可以严格地记录下来,并与他们提出的解决方案一起记录下来。设计模式最显著的应用是解决软件设计中的体系结构问题。
用克里斯托弗·亚历山大自己的话来说:
“该语言的元素是称为模式的实体。每个模式描述了在我们的环境中反复出现的问题,然后描述了该问题解决方案的核心,这样您就可以多次使用该解决方案,而不必以同样的方式重复两次。”
亚历山大在四人帮之前写了自己的书,名为《模式语言》。在这本书中,亚历山大创造了自己的语言,他创造了短语模式语言来描述这一点;这种语言是由架构模式的构建块形成的。通过利用这些架构模式,本书提出普通人可以使用这种语言作为框架来改善他们的社区和城镇。
书中记录的一种模式是模式 12,称为 7000 人社区;本书通过以下内容记录了这种模式:
“个人在超过 5000-10000 人的社区中没有有效的发言权。”
通过使用此类问题及其书面解决方案;这本书最终形成了模式,这些模式试图作为构建更好社区的基石。
正如我提到的,亚历山大早于四人帮;但他的工作对于为软件设计模式播下种子至关重要。
现在,让我们直接转到被称为四人帮的作者。
不,我们不是指 1981 年英国工党或英国后朋克乐队的叛逃者;但我们谈论的是《设计模式:可重用面向对象软件的元素》一书的作者。这本书在软件开发领域有很高的影响力,在软件工程领域也很有名。
在本书的第一章中,作者从自己的个人经验讨论了面向对象的软件开发;这包括争论软件开发人员应该如何为接口而不是实现编程。这将导致代码最终利用面向对象编程的中心功能。
这是一个常见的误解,认为这本书只包含四种设计模式,这是不正确的;它涵盖了三个基本类别的 23 种设计模式。
让我们介绍一下这些类别:
让我们把每一个都分解一下。
创造性设计模式涉及对象本身的创建。在不使用设计模式的情况下对类进行基本实例化可能会导致不必要的复杂性,但也会导致严重的设计问题。
创造性设计模式的主要用途是将类的实例化与实例的使用分开。未能使用创造性设计模式可能意味着您的代码更难理解和测试。
依赖项注入是一个过程,通过该过程,您可以将应用程序所需的依赖项直接输入到对象本身中。
John Munsch 在 Stack Overflow 上留下了一个答案,名为针对五岁儿童的依赖注入,这个答案在Mark Seeman 在.NET中的依赖注入一书中重新发表:
当你自己去把冰箱里的东西拿出来时,你可能会引起问题。你可能会把门开着,你可能会得到妈妈或爸爸不想要的东西。你甚至可能在寻找我们甚至没有的东西或者已经过期的东西。
你应该做的是陈述一个需求,“我午餐需要喝点东西”,然后我们会确保你坐下吃饭时有东西。
在编写类时,使用其他依赖项是很自然的;可能是一个数据库模型类。因此,使用依赖项注入,您可以在对象外部创建它并将其注入,而不是在类自身创建它的数据库模型。简言之,我们将客户的行为与客户的依赖性分开。
在考虑依赖注入时,让我们概括一下所涉及的四个独立角色:
结构设计模式相当容易解释,它们充当实体之间的互连器。它是如何将基本类组合成更大实体的蓝图,所有结构设计模式都涉及对象之间的互连。
行为设计模式用于解释对象如何相互作用;它们如何在每个对象之间发送消息,以及如何在类之间划分各种任务的步骤。
结构模式描述设计的静态架构;行为模式更具流动性,描述流动过程。
这并不是严格意义上的设计模式(但四人帮在他们的书中没有涉及架构模式);但由于 PHP 面向 web 的特性,它与 PHP 开发人员的关系非常密切。体系结构模式通过解决性能限制、高可用性以及最小化业务风险来解决计算机系统中的各种不同约束。
当涉及到 web 框架时,大多数开发人员将熟悉模型-视图-控制器体系结构,最近其他体系结构已经开始出现;例如,微服务体系结构通过一组独立且相互连接的 RESTful API 工作。一些人认为微服务将问题从软件开发层转移到系统架构层。与通常被称为单片架构的微服务相反的是,所有代码都集中在一个应用程序中。
在本章中,我们修改了一些 PHP 原则,包括 OOP 原则。我们还修改了一些 PHP 语法基础。我们已经了解了如何在 PHP 中使用 Composer 进行依赖项管理。除此之外,我们还讨论了 PSR 标准,以及如何在自己的代码中实现这些标准,以使您的代码更易于他人阅读,并遵守其他一些重要标准(自动加载或 HTTP 消息传递)。最后,我们介绍了设计模式和四人帮,以及设计模式背后的历史。