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>

通過使用CustomersRepository,usersAction不需要關心數據從哪來,怎么獲取數據。

但是上面的架構仍然會有些問題:

  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. 使用適配器:不直接依賴于第三方庫,而是使用適配器的方式,減少對于不可控類的依賴
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容