设计模式中的设计原则是指导软件开发人员进行软件设计的一般性准则,遵循这些原则可以提高软件的可维护性、可扩展性、可复用性等。下面详细介绍常见的几个设计原则及其应用场景。
单一职责原则(Single Responsibility Principle,SRP)
定义
一个类应该有且仅有一个引起它变化的原因,即一个类只负责一项职责。如果一个类承担的职责过多,就会导致职责耦合,一个职责的变化可能会影响到其他职责。
解释说明
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力,当变化发生时,设计会遭到意想不到的破坏。
应用场景举例
在一个电商系统中,有一个 User 类,最初设计时它既负责用户信息的管理(如保存、修改用户的基本信息),又负责用户登录和注册的业务逻辑。按照单一职责原则,我们可以将用户信息管理的功能封装到一个 UserInfoManager 类中,将用户登录和注册的功能封装到一个 UserAuthManager 类中。这样,当用户信息管理的业务规则发生变化时,只需要修改 UserInfoManager 类,而不会影响到用户登录和注册的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| <?php
class User { private $id; private $username; private $email;
public function __construct($id, $username, $email) { $this->id = $id; $this->username = $username; $this->email = $email; }
public function getId() { return $this->id; }
public function getUsername() { return $this->username; }
public function getEmail() { return $this->email; }
public function setUsername($username) { $this->username = $username; }
public function setEmail($email) { $this->email = $email; } }
class UserInfoManager { public function saveUserInfo(User $user) { echo "保存用户信息:ID={$user->getId()}, 用户名={$user->getUsername()}, 邮箱={$user->getEmail()}\n"; }
public function updateUserInfo(User $user) { echo "更新用户信息:ID={$user->getId()}, 用户名={$user->getUsername()}, 邮箱={$user->getEmail()}\n"; } }
class UserAuthManager { public function registerUser($username, $email) { $userId = rand(1, 1000); $user = new User($userId, $username, $email); echo "用户注册成功:ID={$user->getId()}, 用户名={$user->getUsername()}, 邮箱={$user->getEmail()}\n"; return $user; }
public function loginUser($username, $email) { echo "用户尝试登录:用户名={$username}, 邮箱={$email}\n"; echo "用户登录成功\n"; } }
$userAuthManager = new UserAuthManager();
$newUser = $userAuthManager->registerUser("john_doe", "john@example.com");
$userInfoManager = new UserInfoManager();
$userInfoManager->saveUserInfo($newUser);
$newUser->setUsername("jane_doe"); $newUser->setEmail("jane@example.com");
$userInfoManager->updateUserInfo($newUser);
$userAuthManager->loginUser("jane_doe", "jane@example.com"); ?>
|
开闭原则(Open Closed Principle,OCP)
定义
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,在不修改现有代码的基础上,通过扩展来实现新的功能。
解释
对一个已完成的类,如果需要修改或增加其中的功能,最好使用继承的方式对其扩展,尽量不要改动类的本身。
应用场景举例
假设我们有一个图形绘制系统,最初只有绘制圆形和矩形的功能。我们可以定义一个抽象的 Shape 类,其中包含一个抽象的 draw 方法,然后让 Circle 类和 Rectangle 类继承自 Shape 类,并实现 draw 方法。当需要添加绘制三角形的功能时,我们不需要修改现有的 Shape、Circle 和 Rectangle 类,只需要创建一个新的 Triangle 类,继承自 Shape 类并实现 draw 方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| <?php
abstract class Shape { abstract public function draw(); }
class Circle extends Shape { private $radius;
public function __construct($radius) { $this->radius = $radius; }
public function draw() { echo "绘制一个半径为 {$this->radius} 的圆形。\n"; } }
class Rectangle extends Shape { private $width; private $height;
public function __construct($width, $height) { $this->width = $width; $this->height = $height; }
public function draw() { echo "绘制一个宽为 {$this->width},高为 {$this->height} 的矩形。\n"; } }
class Triangle extends Shape { private $side1; private $side2; private $side3;
public function __construct($side1, $side2, $side3) { $this->side1 = $side1; $this->side2 = $side2; $this->side3 = $side3; }
public function draw() { echo "绘制一个边长分别为 {$this->side1}、{$this->side2}、{$this->side3} 的三角形。\n"; } }
function drawShape(Shape $shape) { $shape->draw(); }
$circle = new Circle(5);
drawShape($circle);
$rectangle = new Rectangle(4, 6);
drawShape($rectangle);
$triangle = new Triangle(3, 4, 5);
drawShape($triangle); ?>
|
里氏替换原则(Liskov Substitution Principle,LSP)
定义
子类可以替换其父类并且出现在父类能够出现的任何地方,而不会影响程序的正确性。也就是说,子类应该能够完全替代父类,并且不会破坏原有的程序逻辑。
解释
子类型必须能够替换掉他们的父类型。
应用场景举例
在一个几何图形系统中,有一个 Rectangle 类和一个 Square 类。从数学角度看,正方形是一种特殊的长方形,但如果在代码中简单地让 Square 类继承自 Rectangle 类,可能会违反里氏替换原则。因为长方形的长和宽可以独立变化,而正方形的边长是相等的。如果一个方法接受一个 Rectangle 对象作为参数,并对其长和宽进行独立操作,当传入一个 Square 对象时,就可能会导致逻辑错误。为了遵循里氏替换原则,我们可以重新设计类的结构,让 Rectangle 和 Square 都继承自一个更抽象的 Quadrilateral 类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| <?php
abstract class Quadrilateral { abstract public function getArea(); }
class Rectangle extends Quadrilateral { protected $width; protected $height;
public function __construct($width, $height) { $this->width = $width; $this->height = $height; }
public function setWidth($width) { $this->width = $width; }
public function setHeight($height) { $this->height = $height; }
public function getArea() { return $this->width * $this->height; } }
class Square extends Quadrilateral { protected $side;
public function __construct($side) { $this->side = $side; }
public function setSide($side) { $this->side = $side; }
public function getArea() { return $this->side * $this->side; } }
function calculateArea(Quadrilateral $quadrilateral) { return $quadrilateral->getArea(); }
$rectangle = new Rectangle(5, 10);
$rectangleArea = calculateArea($rectangle); echo "长方形的面积是: ". $rectangleArea. "\n";
$square = new Square(7);
$squareArea = calculateArea($square); echo "正方形的面积是: ". $squareArea. "\n"; ?>
|
依赖倒转原则(DIP)
定义
高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。简单来说,就是要依赖接口或抽象类,而不是具体的实现类。
解释
主要有两个原则:
- 高层模块不应该依赖底层模块。两个应该依赖抽象
- 抽象不应该依赖细节,细节应该依赖抽象
应用场景举例
在一个电商系统中,有一个 OrderService 类负责处理订单业务,最初它直接依赖于 MySQLOrderDao 类来实现订单数据的持久化。按照依赖倒置原则,我们可以定义一个抽象的 OrderDao 接口,其中包含订单数据操作的方法,然后让 MySQLOrderDao 类实现这个接口。OrderService 类只依赖于 OrderDao 接口,而不依赖于具体的 MySQLOrderDao 类。这样,当需要更换数据库为 Oracle 时,只需要创建一个实现 OrderDao 接口的 OracleOrderDao 类,并在 OrderService 中注入该类的实例即可,而不需要修改 OrderService 类的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <?php
interface OrderDao { public function saveOrder($order); public function getOrder($orderId); }
class MySQLOrderDao implements OrderDao { public function saveOrder($order) { echo "将订单 {$order} 保存到 MySQL 数据库。\n"; }
public function getOrder($orderId) { echo "从 MySQL 数据库获取订单 ID 为 {$orderId} 的订单。\n"; return "订单详情:ID 为 {$orderId} 的订单信息"; } }
class OracleOrderDao implements OrderDao { public function saveOrder($order) { echo "将订单 {$order} 保存到 Oracle 数据库。\n"; }
public function getOrder($orderId) { echo "从 Oracle 数据库获取订单 ID 为 {$orderId} 的订单。\n"; return "订单详情:ID 为 {$orderId} 的订单信息"; } }
class OrderService { private $orderDao;
public function __construct(OrderDao $orderDao) { $this->orderDao = $orderDao; }
public function createOrder($order) { $this->orderDao->saveOrder($order); }
public function getOrderDetails($orderId) { return $this->orderDao->getOrder($orderId); } }
$mysqlOrderDao = new MySQLOrderDao(); $orderServiceWithMySQL = new OrderService($mysqlOrderDao); $orderServiceWithMySQL->createOrder("订单1"); $orderDetailsFromMySQL = $orderServiceWithMySQL->getOrderDetails(1); echo $orderDetailsFromMySQL . "\n";
$oracleOrderDao = new OracleOrderDao(); $orderServiceWithOracle = new OrderService($oracleOrderDao); $orderServiceWithOracle->createOrder("订单2"); $orderDetailsFromOracle = $orderServiceWithOracle->getOrderDetails(2); echo $orderDetailsFromOracle . "\n"; ?>
|
接口隔离原则(Interface Segregation Principle,ISP)
定义
客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。也就是说,要将大的接口拆分成多个小的、具体的接口,让客户端只依赖它需要的接口。
应用场景举例
在一个系统中,有一个 Employee 接口,其中包含了 work、attendMeeting、writeReport 等方法。对于普通员工来说,可能只需要实现 work 方法;而对于管理人员来说,可能需要实现 attendMeeting 和 writeReport 方法。如果让所有员工类都实现这个大的 Employee 接口,会导致一些类实现了它们不需要的方法。按照接口隔离原则,我们可以将 Employee 接口拆分成 Worker 接口(包含 work 方法)、Manager 接口(包含 attendMeeting 和 writeReport 方法),让不同类型的员工类只实现它们需要的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?php
interface Worker { public function work(); }
interface Manager { public function attendMeeting(); public function writeReport(); }
class RegularEmployee implements Worker { public function work() { echo "普通员工正在努力工作。\n"; } }
class ManagerEmployee implements Manager { public function attendMeeting() { echo "管理人员正在参加会议。\n"; }
public function writeReport() { echo "管理人员正在撰写报告。\n"; } }
$regularEmployee = new RegularEmployee(); $regularEmployee->work();
$managerEmployee = new ManagerEmployee(); $managerEmployee->attendMeeting(); $managerEmployee->writeReport(); ?>
|
迪米特法则(Law of Demeter,LoD)
也称为最少知识原则
定义
一个对象应该对其他对象有最少的了解。也就是说,一个类应该尽量减少与其他类的直接交互,如果两个类之间不必直接通信,那么这两个类就不应该发生直接的相互作用。如果需要通信,可以通过第三方来转发。
应用场景举例
在一个学校管理系统中,有一个 Student 类和一个 Teacher 类,最初 Student 类需要获取 Teacher 类的一些信息,直接与 Teacher 类进行交互。按照迪米特法则,我们可以引入一个 SchoolManager 类作为第三方,Student 类只与 SchoolManager 类进行交互,由 SchoolManager 类来负责与 Teacher 类进行通信并获取所需信息。这样,Student 类就不需要了解 Teacher 类的具体实现细节,减少了类之间的耦合度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| <?php
class Teacher { private $name; private $subject;
public function __construct($name, $subject) { $this->name = $name; $this->subject = $subject; }
public function getName() { return $this->name; }
public function getSubject() { return $this->subject; } }
class SchoolManager { private $teachers = [];
public function addTeacher(Teacher $teacher) { $this->teachers[] = $teacher; }
public function getTeacherInfo($teacherIndex) { if (isset($this->teachers[$teacherIndex])) { $teacher = $this->teachers[$teacherIndex]; return [ 'name' => $teacher->getName(), 'subject' => $teacher->getSubject() ]; } return null; } }
class Student { private $schoolManager;
public function __construct(SchoolManager $schoolManager) { $this->schoolManager = $schoolManager; }
public function getTeacherDetails($teacherIndex) { return $this->schoolManager->getTeacherInfo($teacherIndex); } }
$schoolManager = new SchoolManager();
$teacher1 = new Teacher("张老师", "数学"); $teacher2 = new Teacher("李老师", "英语"); $schoolManager->addTeacher($teacher1); $schoolManager->addTeacher($teacher2);
$student = new Student($schoolManager);
$teacherDetails = $student->getTeacherDetails(0); if ($teacherDetails) { echo "教师姓名: ". $teacherDetails['name']. "\n"; echo "教授科目: ". $teacherDetails['subject']. "\n"; } ?>
|
合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
定义
尽量使用合成 / 聚合,而不是使用继承来达到复用的目的。合成(Composition)和聚合(Aggregation)是关联关系的两种特殊情况,它们都表示整体与部分的关系,但程度有所不同。
解释
- 聚合:是一种弱的 “拥有” 关系,体现的是 A 对象可以包含 B 对象,但 B 对象不是 A 对象的一部分,部分可以脱离整体而存在。例如,学校和老师之间就是聚合关系,老师可以独立于学校而存在,即使学校解散了,老师依然可以存在。
- 合成:是一种强的 “拥有” 关系,体现了严格的部分和整体的关系,部分和整体的生命周期是一致的。例如,人和心脏的关系就是合成关系,心脏是人的一部分,心脏不能脱离人而单独存在。
应用场景举例
聚合场景
在公司管理系统中,公司(Company)和员工(Employee)之间是聚合关系。公司拥有员工,但员工可以独立于公司存在,即使公司倒闭,员工依然可以在其他地方工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <?php
class Employee { private $name;
public function __construct($name) { $this->name = $name; }
public function getName() { return $this->name; } }
class Company { private $employees = [];
public function addEmployee(Employee $employee) { $this->employees[] = $employee; }
public function displayEmployees() { foreach ($this->employees as $employee) { echo $employee->getName() . "\n"; } } }
$employee1 = new Employee("张三"); $employee2 = new Employee("李四");
$company = new Company(); $company->addEmployee($employee1); $company->addEmployee($employee2);
$company->displayEmployees(); ?>
|
合成场景
在电脑系统中,电脑(Computer)和 CPU(Central Processing Unit)之间是合成关系。CPU 是电脑的核心组成部分,没有 CPU 电脑就无法正常工作,并且 CPU 的生命周期和电脑的生命周期是一致的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
class CPU { private $model;
public function __construct($model) { $this->model = $model; }
public function run() { echo $this->model . " CPU 正在运行\n"; } }
class Computer { private $cpu;
public function __construct($cpuModel) { $this->cpu = new CPU($cpuModel); }
public function start() { $this->cpu->run(); } }
$computer = new Computer("Intel Core i7"); $computer->start(); ?>
|