设计原则
软件设计中的 SOLID 原则及常用设计原则
问题
什么是 SOLID 原则?前端开发中有哪些常用的设计原则?
解答
SOLID 原则
1. 单一职责原则 (SRP)
一个类或模块只负责一件事。
// ❌ 违反 SRP:一个类做了太多事
class User {
constructor(name) {
this.name = name;
}
saveToDatabase() { /* 保存到数据库 */ }
generateReport() { /* 生成报告 */ }
sendEmail() { /* 发送邮件 */ }
}
// ✅ 遵循 SRP:职责分离
class User {
constructor(name) {
this.name = name;
}
}
class UserRepository {
save(user) { /* 保存到数据库 */ }
}
class UserReportGenerator {
generate(user) { /* 生成报告 */ }
}
class EmailService {
send(to, content) { /* 发送邮件 */ }
}
2. 开闭原则 (OCP)
对扩展开放,对修改关闭。
// ❌ 违反 OCP:添加新类型需要修改函数
function calculateArea(shape) {
if (shape.type === 'circle') {
return Math.PI * shape.radius ** 2;
} else if (shape.type === 'rectangle') {
return shape.width * shape.height;
}
// 添加新形状需要修改这个函数
}
// ✅ 遵循 OCP:通过扩展添加新功能
class Shape {
area() {
throw new Error('需要实现 area 方法');
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
// 添加新形状只需新增类,无需修改现有代码
class Triangle extends Shape {
constructor(base, height) {
super();
this.base = base;
this.height = height;
}
area() {
return (this.base * this.height) / 2;
}
}
3. 里氏替换原则 (LSP)
子类必须能够替换父类。
// ❌ 违反 LSP:子类改变了父类的行为
class Bird {
fly() {
return '飞行中';
}
}
class Penguin extends Bird {
fly() {
throw new Error('企鹅不会飞'); // 破坏了父类契约
}
}
// ✅ 遵循 LSP:正确的抽象
class Bird {
move() {
throw new Error('需要实现 move 方法');
}
}
class FlyingBird extends Bird {
move() {
return '飞行中';
}
}
class SwimmingBird extends Bird {
move() {
return '游泳中';
}
}
class Sparrow extends FlyingBird {}
class Penguin extends SwimmingBird {}
4. 接口隔离原则 (ISP)
不应该强迫客户端依赖它不需要的接口。
// ❌ 违反 ISP:接口过于臃肿
interface Worker {
work(): void;
eat(): void;
sleep(): void;
}
class Robot implements Worker {
work() { /* 工作 */ }
eat() { /* 机器人不需要吃饭 */ }
sleep() { /* 机器人不需要睡觉 */ }
}
// ✅ 遵循 ISP:接口拆分
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
class Human implements Workable, Eatable, Sleepable {
work() { /* 工作 */ }
eat() { /* 吃饭 */ }
sleep() { /* 睡觉 */ }
}
class Robot implements Workable {
work() { /* 工作 */ }
}
5. 依赖倒置原则 (DIP)
高层模块不应该依赖低层模块,两者都应该依赖抽象。
// ❌ 违反 DIP:高层直接依赖低层实现
class MySQLDatabase {
save(data) { /* 保存到 MySQL */ }
}
class UserService {
constructor() {
this.db = new MySQLDatabase(); // 直接依赖具体实现
}
createUser(user) {
this.db.save(user);
}
}
// ✅ 遵循 DIP:依赖抽象
class Database {
save(data) {
throw new Error('需要实现 save 方法');
}
}
class MySQLDatabase extends Database {
save(data) { /* 保存到 MySQL */ }
}
class MongoDB extends Database {
save(data) { /* 保存到 MongoDB */ }
}
class UserService {
constructor(database) {
this.db = database; // 依赖注入,依赖抽象
}
createUser(user) {
this.db.save(user);
}
}
// 使用时注入具体实现
const userService = new UserService(new MySQLDatabase());
其他常用原则
DRY (Don’t Repeat Yourself)
// ❌ 重复代码
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
function validateUserEmail(user) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(user.email);
}
// ✅ 复用代码
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function validateEmail(email) {
return EMAIL_REGEX.test(email);
}
function validateUserEmail(user) {
return validateEmail(user.email);
}
KISS (Keep It Simple, Stupid)
// ❌ 过度复杂
function isEven(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num % 2 === 0);
}, 0);
});
}
// ✅ 保持简单
function isEven(num) {
return num % 2 === 0;
}
YAGNI (You Aren’t Gonna Need It)
// ❌ 过度设计:添加了当前不需要的功能
class User {
constructor(name) {
this.name = name;
this.permissions = [];
this.roles = [];
this.preferences = {};
this.activityLog = [];
// ... 很多当前用不到的属性
}
}
// ✅ 只实现当前需要的
class User {
constructor(name) {
this.name = name;
}
}
关键点
- SRP:一个模块只做一件事,降低耦合
- OCP:通过扩展而非修改来添加功能
- LSP:子类必须能完全替代父类使用
- ISP:接口要小而专一,避免臃肿
- DIP:依赖抽象而非具体实现,便于测试和替换
- DRY:避免重复代码,提取公共逻辑
- KISS:保持简单,不要过度设计
目录