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的类的例子:

  1. class Ticket {
  2. const SEVERITY_LOW = 'low';
  3. const SEVERITY_HIGH = 'high';
  4. // ...
  5. protected $title;
  6. protected $severity;
  7. protected $status;
  8. protected $conn;
  9. public function __construct(\PDO $conn) {
  10. $this->conn = $conn;
  11. }
  12. public function setTitle($title) {
  13. $this->title = $title;
  14. }
  15. public function setSeverity($severity) {
  16. $this->severity = $severity;
  17. }
  18. public function setStatus($status) {
  19. $this->status = $status;
  20. }
  21. private function validate() {
  22. // Implementation...
  23. }
  24. public function save() {
  25. if ($this->validate()) {
  26. // Implementation...
  27. }
  28. }
  29. }
  30. // Client
  31. $conn = new PDO(/* ... */);
  32. $ticket = new Ticket($conn);
  33. $ticket->setTitle('Checkout not working!');
  34. $ticket->setStatus(Ticket::STATUS_OPEN);
  35. $ticket->setSeverity(Ticket::SEVERITY_HIGH);
  36. $ticket->save();

Ticket类处理的是 ticket 实体的验证和保存到数据库。这两个职责是它改变的两个原因。每当关于票据验证,或者关于票据保存的需求发生变化时,就必须修改Ticket 类。为了解决这里的SRP违规,我们可以使用辅助类和接口来分割职责。

下面是一个重构后的实现实例,符合SRP的要求。

  1. interface KeyValuePersistentMembers {
  2. public function toArray();
  3. }
  4. class Ticket implements KeyValuePersistentMembers {
  5. const STATUS_OPEN = 'open';
  6. const SEVERITY_HIGH = 'high';
  7. //...
  8. protected $title;
  9. protected $severity;
  10. protected $status;
  11. public function setTitle($title) {
  12. $this->title = $title;
  13. }
  14. public function setSeverity($severity) {
  15. $this->severity = $severity;
  16. }
  17. public function setStatus($status) {
  18. $this->status = $status;
  19. }
  20. public function toArray() {
  21. // Implementation...
  22. }
  23. }
  24. class EntityManager {
  25. protected $conn;
  26. public function __construct(\PDO $conn) {
  27. $this->conn = $conn;
  28. }
  29. public function save(KeyValuePersistentMembers $entity)
  30. {
  31. // Implementation...
  32. }
  33. }
  34. class Validator {
  35. public function validate(KeyValuePersistentMembers $entity) {
  36. // Implementation...
  37. }
  38. }
  39. // Client
  40. $conn = new PDO(/* ... */);
  41. $ticket = new Ticket();
  42. $ticket->setTitle('Payment not working!');
  43. $ticket->setStatus(Ticket::STATUS_OPEN);
  44. $ticket->setSeverity(Ticket::SEVERITY_HIGH);
  45. $validator = new Validator();
  46. if ($validator->validate($ticket)) {
  47. $entityManager = new EntityManager($conn);
  48. $entityManager->save($ticket);
  49. }

在这里,我们引入了一个简单的KeyValuePersistentMembers接口,它只有一个toArray方法,然后与EntityManagerValidator类一起使用,现在这两个类都只承担一个职责。Ticket类变成了一个简单的数据持有模型,而客户端现在控制实例化、验证和保存为三个不同的步骤。虽然这肯定不是如何分离职责的通用公式,但它确实提供了一个简单而清晰的例子。

以单一职责原则为前提进行设计,可以产生更小的类,具有更高的可读性和更容易测试代码。