The Clean Architecture in PHP 读书笔记(一)

本书的目的是解决如何构建一个中大型应用,并且满足:

  1. 可测性
  2. 可重构
  3. 易处理
  4. 易维护

而对小的应用,不适合本书的原则,本书在组织上按照:

  • 先介绍平时写PHP代码遇到的共性问题,然后给出为什么good, solid,clean code是对于应用的健壮和可维护非常重要
  • 接着介绍了一些原则和设计模式
  • 最后使用这些原则,介绍了Clean Architecture

先介绍第一部分:The Problem With Code

Writing Good Code is Hard

If it were easy, everyone would be doing it

框架是非常好的,可以帮助我们快速的开发,但是前期的学习成本往往很高,特别是如果想要深入理解框架,需要花费大量的经历。

框架的选择上也非常有讲究,每年都有新的框架产生、消亡,我们要选择那些文档好的,活力好的框架,并且框架不应该限制的应用太死,这样我们的应用能快速的从一个框架切换到另一个框架。

另一个重要的议题是:库函数的使用。

composer和packagist的出现,让我们能更方便的使用各种各样的库和函数,但是使用库同样也会和框架一个问题,当库做升级和废弃的时候,我们需要花费精力去迁移、升级库函数。

以上所有的问题都是本书希望能解决的,本书会通过架构来尝试解决这些问题。

What is Architecture?

我们写任何程序的时候,都会按照某种形式组织,而软件架构就是我们组织程序的方式,当然这种组织是为了更好的达成软件的目标。

What does Architecture Look Like?

我们应用的所有特性定义了软件架构,这些特性可能是:

  • 文件的组织方式
  • PHP代码和Html代码怎么交互
  • 面向过程 or 面向对象
  • 等等....

所以定义架构可能非常的冗长,因此我们会针对一些特点给架构起个名字,方便彼此交流,同时运用这些通用的架构模式,也能使我们写出更容易阅读和理解的代码。

举个具体的例子:你可能只要说我在前端使用MVC模式,后端使用API web service,别人就能很容易的理解你整个应用的组织方式了。

Layers of Software

在面向对象编程中,分层架构中的层往往是将功能相同的类放到一起,而分层往往是根据应用的功能进行划分的。虽然每个应用分层会各不相同,但是一般都会有:数据库交互层,业务层,api交互。

好的分层架构中,彼此间松耦合,内部高内聚。

Examples of Poor Architecture

看好的之前,先看看坏的,通过分析坏的能帮我们更好的理解为什么要这么去做。

Dirty,In-line PHP

<body>
  <?php $results = mysql_query(
    'SELECT * FROM customers ORDER BY name'
); ?>
  <h2>Customers</h2>
  <ul>
<?php while ($customer = mysql_fetch_assoc($results)): ?> <li><?= $customer['name'] ?></li>
<?php endwhile; ?>
  </ul>
</body>

问题:

  1. mysql_*函数已经废弃,使得我们升级PHP变得困难

    Choosing to use these functions today is choosing heartache tomorrow

  2. 一层就解决了所有的事

    将应用所有的事情都在一层中解决了!应用主要有两个关注点:一个是从数据库中获取数据,另一个是是对数据进行展示。

  3. 重构的噩梦

    考虑下面的变更

    • 表名(customers)或者列名(name)变了怎么办?有多少文件你需要去修改?如果我们要从mysql_换到PDO怎么办?如果数据不再是从数据库中,而是从Restful API?
    • 如果我们开始使用模块语言,如Twig或者Blade?我们的数据库逻辑深嵌入Html代码中,我们必须要重写所有代码
    • 如果我们想改变名字的显示方式,我们需要更改多少地方?
  4. 代码不可测

Poor Man's MVC

看完用PHP裸写应用后,进一步是使用mvc模式,下面是一个例子:

class CustomersController { public function indexAction() {
    $db = Db::getInstance();
    $customers = $db->fetchAll(
      'SELECT * FROM customers ORDER BY name'
);
return [
'customers' => $customers
]; }
}
<h2>Customers</h2>
<ul>
<?php foreach ($this->customers as $customer): ?> <li><?= $customer['name'] ?></li>
<?php endforeach; ?>
</ul>

我们让显示逻辑和控制逻辑分离了,但是仍然有问题:

  1. 仍然是硬编码Querys
  2. 和Db类强耦合
  3. 仍然很难测试
  4. 分了两个非常大的层

Poor Usage of Database Abstraction

使用Repository设计模式进行重构:

class CustomersController { 
  public function usersAction() {
    $repository = new CustomersRepository(); 
    $customers = $repository->getAll();
    return [
        'customers' => $customers
    ]; 
   }
}
<h2>Customers</h2>
<ul>
<?php foreach ($this->customers as $customer): ?> <li><?= $customer['name'] ?></li>
<?php endforeach; ?>
</ul>

通过使用CustomersRepositoryusersAction不需要关心数据从哪来,怎么获取数据。

但是上面的架构仍然会有些问题:

  1. CustomersRepository强耦合

    仍然直接实例化出CustomersRepository类,意味着依赖于具体实现,而不是抽象。

  2. 依赖问题

    由于我们仍然依赖于具体的类,因此在测试时候,不适合单元测试。

So how Should this Code Look?

class CustomersController extends AbstractActionController { 
  protected $customerRepository;
public function __construct(CustomerRepositoryInterface $repository) { 
  $this->customerRepository = $repository;
}
public function indexAction() { 
  return [
      'users' => $this->customerRepository->getAll()
    ];
} 
}

上面的代码解决了几个问题:

  1. 通过声明依赖于接口,我们再也不依赖于具体实现了
  2. 可测性好,通过实现不同的CustomerRepositoryInterface,我们就能模拟各种case
  3. 没有地方会影响我们升级新的PHP或者函数库
  4. 数据来源的变化再也不会影响我们了

Coupling The Enemy

耦合是我们遇到的问题中最普遍的,我们为了更好的理解耦合,看两个例子:

Spaghetti Coupling

<body>
<?php $users = mysqli_query('SELECT * FROM users'); ?>
<ul>
<?php foreach ($users as $user): ?> <li><?= $user['name'] ?></li> <?php endforeach; ?>
  </ul>
</body>

上面的代码耦合非常严重,高耦合意味着一旦离开另一个类或功能,将无法工作。上面的例子:一旦离开database,我们不能正常工作了,一旦离开浏览器,我们也无法正常显示用户信息。

OOP Coupling

class UsersController {
public function indexAction() {
  $repo = new UserRepository(); 
  $users = $repo->getAll();
  return $users; 
}
}

UsersController离开UserRepository能工作吗?不能,因此UsersController强依赖于UserRepository

低耦合谁特别关心?

  • Developers who refactor their code.
  • Developers who like to test their code
  • Developers who like to reuse their code

How do we Reduce Coupling?

那怎么减少耦合呢?有下面4个方法

  1. 减少依赖:尽可能将类的职责设计的最少,减少对外部的依赖
  2. 使用依赖注入(DI)
  3. 使用接口,而不是具体的类
  4. 使用适配器:不直接依赖于第三方库,而是使用适配器的方式,减少对于不可控类的依赖
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容