面向对象设计原则与设计模式的关系
引言
在上一章中,我们了解了设计模式的基本概念和分类。设计模式不是凭空产生的,它们是面向对象设计原则的具体应用和体现。要真正掌握设计模式,我们必须首先理解这些设计原则。
正如 Robert C. Martin 在《敏捷软件开发:原则、模式与实践》中所说:"设计模式是原则的具体应用。"设计原则是指导思想,而设计模式是实践方法。
SOLID 原则详解
SOLID 是五个面向对象设计原则的首字母缩写,由 Robert C. Martin 提出。这些原则旨在使软件设计更易于理解、灵活和可维护。
1. 单一职责原则(Single Responsibility Principle, SRP)
定义:一个类应该只有一个引起它变化的原因。
换句话说,一个类应该只有一个职责,只负责一类功能。
违反 SRP 的示例
// 违反单一职责原则的类
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
// 用户数据管理职责
saveToDatabase() {
// 保存用户到数据库的逻辑
console.log(`保存用户 ${this.name} 到数据库`);
}
// 邮件发送职责
sendEmail(message) {
// 发送邮件的逻辑
console.log(`发送邮件给 ${this.email}: ${message}`);
}
// 数据验证职责
validateEmail() {
// 验证邮箱格式的逻辑
return /\S+@\S+\.\S+/.test(this.email);
}
// 日志记录职责
logActivity(activity) {
// 记录用户活动的逻辑
console.log(`用户 ${this.name} 执行了: ${activity}`);
}
}遵循 SRP 的改进
// 遵循单一职责原则的类
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRepository {
save(user) {
console.log(`保存用户 ${user.name} 到数据库`);
}
}
class EmailService {
send(email, message) {
console.log(`发送邮件给 ${email}: ${message}`);
}
}
class EmailValidator {
static validate(email) {
return /\S+@\S+\.\S+/.test(email);
}
}
class ActivityLogger {
log(user, activity) {
console.log(`用户 ${user.name} 执行了: ${activity}`);
}
}
// 使用示例
const user = new User('Alice', 'alice@example.com');
const validator = new EmailValidator();
const repository = new UserRepository();
const emailService = new EmailService();
const logger = new ActivityLogger();
if (validator.validate(user.email)) {
repository.save(user);
emailService.send(user.email, '欢迎注册!');
logger.log(user, '注册账户');
}SRP 与设计模式的关系
单一职责原则是许多设计模式的基础,例如:
- 策略模式:将不同的算法封装在独立的类中,每个类只有一个职责
- 命令模式:将每个请求封装为一个对象,每个命令类只负责一个操作
- 观察者模式:将观察者和被观察者的职责分离
2. 开闭原则(Open/Closed Principle, OCP)
定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
这意味着我们应该能够不修改现有代码的情况下扩展系统的行为。
违反 OCP 的示例
// 违反开闭原则的代码
class PaymentProcessor {
processPayment(amount, type) {
if (type === 'credit') {
// 处理信用卡支付
console.log(`处理信用卡支付: $${amount}`);
} else if (type === 'debit') {
// 处理借记卡支付
console.log(`处理借记卡支付: $${amount}`);
} else if (type === 'paypal') {
// 处理 PayPal 支付
console.log(`处理 PayPal 支付: $${amount}`);
}
// 每次添加新的支付方式都需要修改这个方法
}
}遵循 OCP 的改进
// 遵循开闭原则的代码
class PaymentProcessor {
processPayment(amount, paymentMethod) {
paymentMethod.process(amount);
}
}
class PaymentMethod {
process(amount) {
throw new Error('必须实现 process 方法');
}
}
class CreditCardPayment extends PaymentMethod {
process(amount) {
console.log(`处理信用卡支付: $${amount}`);
}
}
class DebitCardPayment extends PaymentMethod {
process(amount) {
console.log(`处理借记卡支付: $${amount}`);
}
}
class PayPalPayment extends PaymentMethod {
process(amount) {
console.log(`处理 PayPal 支付: $${amount}`);
}
}
// 添加新的支付方式时,不需要修改现有代码
class WeChatPayment extends PaymentMethod {
process(amount) {
console.log(`处理微信支付: $${amount}`);
}
}
// 使用示例
const processor = new PaymentProcessor();
processor.processPayment(100, new CreditCardPayment());
processor.processPayment(200, new PayPalPayment());
processor.processPayment(150, new WeChatPayment()); // 新增支付方式OCP 与设计模式的关系
开闭原则是许多设计模式的核心思想,例如:
- 策略模式:通过定义一系列算法,将它们封装起来,并且使它们可以互相替换
- 工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪个类
- 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
3. 里氏替换原则(Liskov Substitution Principle, LSP)
定义:子类型必须能够替换它们的基类型。
这意味着在程序中,任何基类出现的地方,子类都应该能够无缝替换,而不会影响程序的正确性。
违反 LSP 的示例
// 违反里氏替换原则的示例
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Rectangle {
constructor(side) {
super(side, side);
}
setWidth(width) {
this.width = width;
this.height = width; // 强制保持正方形
}
setHeight(height) {
this.width = height; // 强制保持正方形
this.height = height;
}
}
// 这个函数期望使用矩形,但使用正方形时行为不一致
function increaseRectangleWidth(rectangle) {
rectangle.setWidth(rectangle.width + 1);
}
const rectangle = new Rectangle(2, 3);
const square = new Square(2);
console.log(rectangle.getArea()); // 6
increaseRectangleWidth(rectangle);
console.log(rectangle.getArea()); // 9 (3*3)
console.log(square.getArea()); // 4
increaseRectangleWidth(square);
console.log(square.getArea()); // 9 (3*3) 但正方形的高也被改变了遵循 LSP 的改进
// 遵循里氏替换原则的改进
class Shape {
getArea() {
throw new Error('必须实现 getArea 方法');
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
getArea() {
return this.side * this.side;
}
}
// 现在每个形状都有明确的职责,不会出现替换问题
function printArea(shape) {
console.log(`面积: ${shape.getArea()}`);
}
const rectangle = new Rectangle(2, 3);
const square = new Square(2);
printArea(rectangle); // 面积: 6
printArea(square); // 面积: 44. 接口隔离原则(Interface Segregation Principle, ISP)
定义:客户端不应该被迫依赖它们不使用的接口。
这个原则强调应该创建更小、更具体的接口,而不是一个庞大的通用接口。
违反 ISP 的示例
// 违反接口隔离原则的示例
class MultiFunctionPrinter {
print(document) {
// 打印文档
}
fax(document) {
// 发送传真
}
scan(document) {
// 扫描文档
}
}
class OldFashionedPrinter extends MultiFunctionPrinter {
print(document) {
// 只能打印
}
// 传真和扫描功能不支持,但接口强制实现
fax(document) {
throw new Error('不支持传真功能');
}
scan(document) {
throw new Error('不支持扫描功能');
}
}遵循 ISP 的改进
// 遵循接口隔离原则的改进
class Printer {
print(document) {
throw new Error('必须实现 print 方法');
}
}
class Scanner {
scan(document) {
throw new Error('必须实现 scan 方法');
}
}
class FaxMachine {
fax(document) {
throw new Error('必须实现 fax 方法');
}
}
// 只需要打印功能的打印机
class SimplePrinter extends Printer {
print(document) {
console.log(`打印文档: ${document}`);
}
}
// 多功能打印机
class MultiFunctionMachine extends Printer {
constructor(printer, scanner, faxMachine) {
super();
this.printer = printer;
this.scanner = scanner;
this.faxMachine = faxMachine;
}
print(document) {
this.printer.print(document);
}
scan(document) {
this.scanner.scan(document);
}
fax(document) {
this.faxMachine.fax(document);
}
}5. 依赖倒置原则(Dependency Inversion Principle, DIP)
定义:
- 高层模块不应该依赖低层模块,二者都应该依赖抽象
- 抽象不应该依赖细节,细节应该依赖抽象
违反 DIP 的示例
// 违反依赖倒置原则的示例
class LightBulb {
turnOn() {
console.log('灯泡打开了');
}
turnOff() {
console.log('灯泡关闭了');
}
}
class ElectricPowerSwitch {
constructor() {
this.lightBulb = new LightBulb(); // 直接依赖具体实现
this.on = false;
}
press() {
if (this.on) {
this.lightBulb.turnOff();
this.on = false;
} else {
this.lightBulb.turnOn();
this.on = true;
}
}
}遵循 DIP 的改进
// 遵循依赖倒置原则的改进
class Switchable {
turnOn() {
throw new Error('必须实现 turnOn 方法');
}
turnOff() {
throw new Error('必须实现 turnOff 方法');
}
}
class LightBulb extends Switchable {
turnOn() {
console.log('灯泡打开了');
}
turnOff() {
console.log('灯泡关闭了');
}
}
class Fan extends Switchable {
turnOn() {
console.log('风扇开始转动');
}
turnOff() {
console.log('风扇停止转动');
}
}
class ElectricPowerSwitch {
constructor(device) {
this.device = device; // 依赖抽象而不是具体实现
this.on = false;
}
press() {
if (this.on) {
this.device.turnOff();
this.on = false;
} else {
this.device.turnOn();
this.on = true;
}
}
}
// 使用示例
const bulb = new LightBulb();
const fan = new Fan();
const switch1 = new ElectricPowerSwitch(bulb);
const switch2 = new ElectricPowerSwitch(fan);
switch1.press(); // 灯泡打开了
switch2.press(); // 风扇开始转动其他重要设计原则
除了 SOLID 原则外,还有一些其他重要的设计原则:
1. DRY 原则(Don't Repeat Yourself)
定义:避免重复代码,将共同的逻辑封装起来。
// 违反 DRY 原则
function calculateCircleArea(radius) {
return 3.14159 * radius * radius;
}
function calculateSphereVolume(radius) {
return (4/3) * 3.14159 * radius * radius * radius;
}
// 遵循 DRY 原则
const PI = 3.14159;
function calculateCircleArea(radius) {
return PI * radius * radius;
}
function calculateSphereVolume(radius) {
return (4/3) * PI * radius * radius * radius;
}2. KISS 原则(Keep It Simple, Stupid)
定义:保持简单,避免过度设计。
// 过度复杂的解决方案
function isEven(number) {
return number % 2 === 0 ? true : false;
}
// 简单直接的解决方案
function isEven(number) {
return number % 2 === 0;
}3. YAGNI 原则(You Aren't Gonna Need It)
定义:不要添加当前不需要的功能。
这个原则提醒我们不要过度设计,只实现当前需要的功能。
总结
面向对象设计原则是设计模式的基础,它们为我们提供了设计高质量软件的指导方针。理解这些原则有助于我们:
- 编写更易维护的代码 - 通过遵循 SRP 和 ISP 等原则
- 创建更灵活的系统 - 通过遵循 OCP 和 DIP 等原则
- 提高代码的可复用性 - 通过遵循 LSP 等原则
- 减少代码的复杂性 - 通过遵循 KISS 和 YAGNI 等原则
在下一章中,我们将探讨设计模式的历史发展,了解它们是如何从 GoF 的经典著作发展到现代 JavaScript 和前端开发中的应用。