本书的目的是解决如何构建一个中大型应用,并且满足:
- 可测性
- 可重构
- 易处理
- 易维护
而对小的应用,不适合本书的原则,本书在组织上按照:
- 先介绍平时写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
Customers
- = $customer['name'] ?>
问题:
-
mysql_*函数已经废弃,使得我们升级PHP变得困难
Choosing to use these functions today is choosing heartache tomorrow
-
一层就解决了所有的事
将应用所有的事情都在一层中解决了!应用主要有两个关注点:一个是从数据库中获取数据,另一个是是对数据进行展示。
-
重构的噩梦
考虑下面的变更
- 表名(customers)或者列名(name)变了怎么办?有多少文件你需要去修改?如果我们要从mysql_换到PDO怎么办?如果数据不再是从数据库中,而是从Restful API?
- 如果我们开始使用模块语言,如Twig或者Blade?我们的数据库逻辑深嵌入Html代码中,我们必须要重写所有代码
- 如果我们想改变名字的显示方式,我们需要更改多少地方?
代码不可测
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
]; }
}
Customers
customers as $customer): ?> - = $customer['name'] ?>
我们让显示逻辑和控制逻辑分离了,但是仍然有问题:
- 仍然是硬编码Querys
- 和Db类强耦合
- 仍然很难测试
- 分了两个非常大的层
Poor Usage of Database Abstraction
使用Repository设计模式进行重构:
class CustomersController {
public function usersAction() {
$repository = new CustomersRepository();
$customers = $repository->getAll();
return [
'customers' => $customers
];
}
}
Customers
customers as $customer): ?> - = $customer['name'] ?>
通过使用CustomersRepository
,usersAction
不需要关心数据从哪来,怎么获取数据。
但是上面的架构仍然会有些问题:
-
和
CustomersRepository
强耦合仍然直接实例化出
CustomersRepository
类,意味着依赖于具体实现,而不是抽象。 -
依赖问题
由于我们仍然依赖于具体的类,因此在测试时候,不适合单元测试。
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()
];
}
}
上面的代码解决了几个问题:
- 通过声明依赖于接口,我们再也不依赖于具体实现了
- 可测性好,通过实现不同的
CustomerRepositoryInterface
,我们就能模拟各种case - 没有地方会影响我们升级新的PHP或者函数库
- 数据来源的变化再也不会影响我们了
Coupling The Enemy
耦合是我们遇到的问题中最普遍的,我们为了更好的理解耦合,看两个例子:
Spaghetti Coupling
- = $user['name'] ?>
上面的代码耦合非常严重,高耦合意味着一旦离开另一个类或功能,将无法工作。上面的例子:一旦离开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个方法
- 减少依赖:尽可能将类的职责设计的最少,减少对外部的依赖
- 使用依赖注入(DI)
- 使用接口,而不是具体的类
- 使用适配器:不直接依赖于第三方库,而是使用适配器的方式,减少对于不可控类的依赖