本書的目的是解決如何構建一個中大型應用,并且滿足:
- 可測性
- 可重構
- 易處理
- 易維護
而對小的應用,不適合本書的原則,本書在組織上按照:
- 先介紹平時寫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>
問題:
-
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
]; }
}
<h2>Customers</h2>
<ul>
<?php foreach ($this->customers as $customer): ?> <li><?= $customer['name'] ?></li>
<?php endforeach; ?>
</ul>
我們讓顯示邏輯和控制邏輯分離了,但是仍然有問題:
- 仍然是硬編碼Querys
- 和Db類強耦合
- 仍然很難測試
- 分了兩個非常大的層
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>
通過使用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
<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個方法
- 減少依賴:盡可能將類的職責設計的最少,減少對外部的依賴
- 使用依賴注入(DI)
- 使用接口,而不是具體的類
- 使用適配器:不直接依賴于第三方庫,而是使用適配器的方式,減少對于不可控類的依賴