SOLID 设计原则
构建模块化软件需要很强的类设计知识。有很多指南,涉及到我们对类的命名方式,类应该有多少个变量,方法的大小应该是多少等等。PHP生态系统设法将这些打包成了官方的PSR标准,更准确地说,是PSR-1:基本编码标准和PSR-2:编码风格指南。这些都是一般的编程指南,让我们的代码可读、可理解、可维护。
除了编程指南外,在类设计过程中,我们还可以应用更具体的设计原则。关于解决低耦合、高内聚和强封装的概念。我们把它们称为SOLID设计原则,这个术语是Robert Cecil Martin在2000年初创造的。
SOLID是以下五个原则的缩写:
- S:单一职责原则(SRP)
- O:开闭原则(OCP)
- L:里氏替换原则(LSP)
- I: 接口隔离原则(ISP)
- D:依赖反转原则(DIP)
SOLID原则的概念已经有十多年的历史了,但它远远没有过时,因为它们是好的类设计的核心。在本章中,我们将研究这些原则中的每一条,通过观察一些明显违反原则的行为来了解它们。
在本章中,我们将涉及以下主题:
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 接口隔离原则
- 依赖反转原则
单一职责原则
单一职责原则处理的是试图做得太多的类。这里的职责是指改变的原因。按照罗伯特-C-马丁的定义。
一个类仅有一个引起它变化的原因
下面是一个违反SRP的类的例子:
class Ticket {const SEVERITY_LOW = 'low';const SEVERITY_HIGH = 'high';// ...protected $title;protected $severity;protected $status;protected $conn;public function __construct(\PDO $conn) {$this->conn = $conn;}public function setTitle($title) {$this->title = $title;}public function setSeverity($severity) {$this->severity = $severity;}public function setStatus($status) {$this->status = $status;}private function validate() {// Implementation...}public function save() {if ($this->validate()) {// Implementation...}}}// Client$conn = new PDO(/* ... */);$ticket = new Ticket($conn);$ticket->setTitle('Checkout not working!');$ticket->setStatus(Ticket::STATUS_OPEN);$ticket->setSeverity(Ticket::SEVERITY_HIGH);$ticket->save();
Ticket类处理的是 ticket 实体的验证和保存到数据库。这两个职责是它改变的两个原因。每当关于票据验证,或者关于票据保存的需求发生变化时,就必须修改Ticket 类。为了解决这里的SRP违规,我们可以使用辅助类和接口来分割职责。
下面是一个重构后的实现实例,符合SRP的要求。
interface KeyValuePersistentMembers {public function toArray();}class Ticket implements KeyValuePersistentMembers {const STATUS_OPEN = 'open';const SEVERITY_HIGH = 'high';//...protected $title;protected $severity;protected $status;public function setTitle($title) {$this->title = $title;}public function setSeverity($severity) {$this->severity = $severity;}public function setStatus($status) {$this->status = $status;}public function toArray() {// Implementation...}}class EntityManager {protected $conn;public function __construct(\PDO $conn) {$this->conn = $conn;}public function save(KeyValuePersistentMembers $entity){// Implementation...}}class Validator {public function validate(KeyValuePersistentMembers $entity) {// Implementation...}}// Client$conn = new PDO(/* ... */);$ticket = new Ticket();$ticket->setTitle('Payment not working!');$ticket->setStatus(Ticket::STATUS_OPEN);$ticket->setSeverity(Ticket::SEVERITY_HIGH);$validator = new Validator();if ($validator->validate($ticket)) {$entityManager = new EntityManager($conn);$entityManager->save($ticket);}
在这里,我们引入了一个简单的KeyValuePersistentMembers接口,它只有一个toArray方法,然后与EntityManager和Validator类一起使用,现在这两个类都只承担一个职责。Ticket类变成了一个简单的数据持有模型,而客户端现在控制实例化、验证和保存为三个不同的步骤。虽然这肯定不是如何分离职责的通用公式,但它确实提供了一个简单而清晰的例子。
以单一职责原则为前提进行设计,可以产生更小的类,具有更高的可读性和更容易测试代码。
