前言
温馨提示
对于原本不太熟悉设计模式的人来说(比如在下),这些内容是需要一定的时间消化的!慢慢来 😆
👋 你好啊,我是你的人类朋友!
今天说说设计模式的原则有哪些!
在开发用户权限系统时,你是否遇到过这样的问题:
当创建新的管理员用户类型时,发现它无法兼容普通用户的所有方法,导致系统中到处需要判断用户类型?
让我们了解设计模式的基本原则,构建更健壮的软件架构~
健壮是啥意思? 健壮是指软件系统在面对变化和复杂性时,能够保持稳定运行的能力。也就是耐造的能力。
正文
SOLID 原则
SOLID 是面向对象设计的五个基本原则。
1. 单一职责原则 (SRP - Single Responsibility Principle)
含义:一个类应该只有一个引起变化的原因。
1/* 违反SRP:订单处理类承担了验证、计算、存储、通知多个职责 */ 2class OrderProcessor { 3 validateOrder(order) { 4 return order.items.length > 0 5 } 6 7 calculateTotal(order) { 8 return order.items.reduce((sum, item) => sum + item.price, 0) 9 } 10 11 saveToDatabase(order) { 12 console.log('保存订单到数据库') 13 } 14 15 sendEmailConfirmation(order) { 16 console.log('发送订单确认邮件') 17 } 18} 19 20// 使用 21const processor = new OrderProcessor() 22processor.validateOrder({items: [{price: 100}]}) 23/* 违反SRP结束 */ 24 25/* 遵循SRP:每个类只负责一个明确职责 */ 26class OrderValidator { 27 validate(order) { 28 return order.items.length > 0 29 } 30} 31 32class OrderCalculator { 33 calculateTotal(order) { 34 return order.items.reduce((sum, item) => sum + item.price, 0) 35 } 36} 37 38class OrderRepository { 39 save(order) { 40 console.log('保存订单到数据库') 41 } 42} 43 44class OrderProcessor { 45 constructor() { 46 this.validator = new OrderValidator() 47 this.calculator = new OrderCalculator() 48 this.repository = new OrderRepository() 49 } 50 51 process(order) { 52 if (!this.validator.validate(order)) return 53 order.total = this.calculator.calculateTotal(order) 54 this.repository.save(order) 55 } 56} 57 58// 使用 59const order = {items: [{price: 100}, {price: 200}]} 60const processor = new OrderProcessor() 61processor.process(order) 62console.log(order.total) // 输出: 300 63/* 遵循SRP结束 */ 64
2. 开闭原则 (OCP - Open-Closed Principle)
含义:软件实体应该对扩展开放,对修改关闭。
1/* 违反OCP:添加新通知类型需要修改原有类 */ 2class NotificationService { 3 send(message, type) { 4 if (type === 'email') { 5 console.log(`发送邮件: ${message}`) 6 } else if (type === 'sms') { 7 console.log(`发送短信: ${message}`) 8 } 9 // 添加新类型需要修改这里 10 } 11} 12 13// 使用 14const notifier = new NotificationService() 15notifier.send('订单创建成功', 'email') 16/* 违反OCP结束 */ 17 18/* 遵循OCP:通过扩展而非修改来支持新类型 */ 19class Notification { 20 send(message) { 21 throw new Error('必须实现send方法') 22 } 23} 24 25class EmailNotification extends Notification { 26 send(message) { 27 console.log(`发送邮件: ${message}`) 28 } 29} 30 31class SMSNotification extends Notification { 32 send(message) { 33 console.log(`发送短信: ${message}`) 34 } 35} 36 37class PushNotification extends Notification { 38 send(message) { 39 console.log(`发送推送: ${message}`) 40 } 41} 42 43class NotificationService { 44 constructor() { 45 this.notifications = [] 46 } 47 48 addNotification(notification) { 49 this.notifications.push(notification) 50 } 51 52 broadcast(message) { 53 this.notifications.forEach(notification => { 54 notification.send(message) 55 }) 56 } 57} 58 59// 使用 60const service = new NotificationService() 61service.addNotification(new EmailNotification()) 62service.addNotification(new SMSNotification()) 63service.broadcast('系统维护通知') 64// 输出: 发送邮件: 系统维护通知 65// 输出: 发送短信: 系统维护通知 66/* 遵循OCP结束 */ 67
3. 里氏替换原则 (LSP - Liskov Substitution Principle)
含义:子类应该能够替换它们的父类,而不影响程序的正确性。
1/* 违反LSP:子类改变了父类的权限检查逻辑 */ 2class User { 3 constructor(permissions) { 4 this.permissions = permissions || [] 5 } 6 7 hasPermission(permission) { 8 return this.permissions.includes(permission) 9 } 10 11 canDeleteContent() { 12 return this.hasPermission('delete') 13 } 14} 15 16class AdminUser extends User { 17 canDeleteContent() { 18 // 违反:管理员绕过权限检查,破坏了父类契约 19 return true 20 } 21} 22 23// 使用 - 会出现不一致行为 24function deleteContent(user, content) { 25 if (user.canDeleteContent()) { 26 console.log(`删除内容: ${content}`) 27 } 28} 29 30const regularUser = new User(['read']) 31const adminUser = new AdminUser([]) // 没有delete权限但可以删除 32deleteContent(regularUser, '文章') // 无输出 33deleteContent(adminUser, '文章') // 输出: 删除内容: 文章 34/* 违反LSP结束 */ 35 36/* 遵循LSP:子类遵守父类的行为契约 */ 37class User { 38 constructor(permissions) { 39 this.permissions = permissions || [] 40 } 41 42 hasPermission(permission) { 43 return this.permissions.includes(permission) 44 } 45 46 canDeleteContent() { 47 return this.hasPermission('delete') 48 } 49} 50 51class AdminUser extends User { 52 canDeleteContent() { 53 // 遵循:仍然进行权限检查,只是权限范围更大 54 return this.hasPermission('delete') || this.hasPermission('admin_delete') 55 } 56} 57 58// 使用 - 行为一致 59const regularUser = new User(['delete']) 60const adminUser = new AdminUser(['admin_delete']) 61deleteContent(regularUser, '文章') // 输出: 删除内容: 文章 62deleteContent(adminUser, '文章') // 输出: 删除内容: 文章 63/* 遵循LSP结束 */ 64
4. 接口隔离原则 (ISP - Interface Segregation Principle)
含义:客户端不应该被迫依赖它们不使用的接口。
1/* 违反ISP:简单商店被迫依赖所有支付方法 */ 2class PaymentProcessor { 3 processCreditCard(amount) { 4 console.log(`信用卡支付: $${amount}`) 5 } 6 processPayPal(amount) { 7 console.log(`PayPal支付: $${amount}`) 8 } 9 processBankTransfer(amount) { 10 console.log(`银行转账: $${amount}`) 11 } 12} 13 14class SimpleStore { 15 constructor() { 16 this.paymentProcessor = new PaymentProcessor() 17 } 18 19 processPayment(amount) { 20 // 只使用信用卡,但依赖了所有支付方法 21 this.paymentProcessor.processCreditCard(amount) 22 } 23} 24 25// 使用 26const store = new SimpleStore() 27store.processPayment(100) // 输出: 信用卡支付: $100 28/* 违反ISP结束 */ 29 30/* 遵循ISP:客户端只依赖需要的接口 */ 31class PaymentProcessor { 32 process(amount) { 33 throw new Error('必须实现process方法') 34 } 35} 36 37class CreditCardProcessor extends PaymentProcessor { 38 process(amount) { 39 console.log(`信用卡支付: $${amount}`) 40 } 41} 42 43class PayPalProcessor extends PaymentProcessor { 44 process(amount) { 45 console.log(`PayPal支付: $${amount}`) 46 } 47} 48 49class SimpleStore { 50 constructor(paymentProcessor) { 51 this.paymentProcessor = paymentProcessor 52 } 53 54 processPayment(amount) { 55 this.paymentProcessor.process(amount) 56 } 57} 58 59// 使用 60const creditStore = new SimpleStore(new CreditCardProcessor()) 61const paypalStore = new SimpleStore(new PayPalProcessor()) 62creditStore.processPayment(100) // 输出: 信用卡支付: $100 63paypalStore.processPayment(200) // 输出: PayPal支付: $200 64/* 遵循ISP结束 */ 65
5. 依赖倒置原则 (DIP - Dependency Inversion Principle)
含义:高层模块不应该依赖低层模块,两者都应该依赖抽象。
1/* 违反DIP:服务层直接依赖具体的数据访问实现 */ 2class MySQLUserRepository { 3 findUserById(id) { 4 console.log([`从MySQL查询用户: ${id}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md)) 5 return {id, name: 'MySQL用户'} 6 } 7} 8 9class UserService { 10 constructor() { 11 // 违反:直接依赖具体实现 12 this.userRepository = new MySQLUserRepository() 13 } 14 15 getUserProfile(id) { 16 return this.userRepository.findUserById(id) 17 } 18} 19 20// 使用 21const service = new UserService() 22const user = service.getUserProfile(1) 23console.log(user.name) // 输出: MySQL用户 24/* 违反DIP结束 */ 25 26/* 遵循DIP:通过抽象解耦依赖关系 */ 27class UserRepository { 28 findUserById(id) { 29 throw new Error('必须实现findUserById方法') 30 } 31} 32 33class MySQLUserRepository extends UserRepository { 34 findUserById(id) { 35 console.log([`从MySQL查询用户: ${id}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md)) 36 return {id, name: 'MySQL用户'} 37 } 38} 39 40class MongoDBUserRepository extends UserRepository { 41 findUserById(id) { 42 console.log([`从MongoDB查询用户: ${id}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md)) 43 return {id, name: 'MongoDB用户'} 44 } 45} 46 47class UserService { 48 constructor(userRepository) { 49 // 遵循:依赖抽象接口 50 this.userRepository = userRepository 51 } 52 53 getUserProfile(id) { 54 return this.userRepository.findUserById(id) 55 } 56} 57 58// 使用 - 可以轻松切换不同的数据源 59const mysqlService = new UserService(new MySQLUserRepository()) 60const mongoService = new UserService(new MongoDBUserRepository()) 61mysqlService.getUserProfile(1) // 输出: 从MySQL查询用户: 1 62mongoService.getUserProfile(1) // 输出: 从MongoDB查询用户: 1 63/* 遵循DIP结束 */ 64
其他重要原则
6. 合成复用原则 (CRP - Composite Reuse Principle)
含义:优先使用对象组合,而不是继承来达到复用的目的。
1/* 违反CRP:使用继承导致类层次复杂 */ 2class Logger { 3 log(message) { 4 console.log(`基础日志: ${message}`) 5 } 6} 7 8class FileLogger extends Logger { 9 log(message) { 10 super.log(message) 11 console.log(`文件日志: ${message}`) 12 } 13} 14 15// 使用 16const fileLogger = new FileLogger() 17fileLogger.log('测试消息') 18/* 违反CRP结束 */ 19 20/* 遵循CRP:使用组合提高灵活性 */ 21class ConsoleWriter { 22 write(message) { 23 console.log(`控制台: ${message}`) 24 } 25} 26 27class FileWriter { 28 write(message) { 29 console.log(`写入文件: ${message}`) 30 } 31} 32 33class DatabaseWriter { 34 write(message) { 35 console.log(`保存到数据库: ${message}`) 36 } 37} 38 39class Logger { 40 constructor(writer) { 41 this.writer = writer 42 } 43 44 log(message) { 45 this.writer.write(message) 46 } 47} 48 49// 使用 - 可以灵活组合不同的写入器 50const consoleLogger = new Logger(new ConsoleWriter()) 51const fileLogger = new Logger(new FileWriter()) 52const dbLogger = new Logger(new DatabaseWriter()) 53 54consoleLogger.log('控制台消息') // 输出: 控制台: 控制台消息 55fileLogger.log('文件消息') // 输出: 写入文件: 文件消息 56dbLogger.log('数据库消息') // 输出: 保存到数据库: 数据库消息 57/* 遵循CRP结束 */ 58
7. 迪米特法则 (LoD - Law of Demeter)
含义:一个对象应该对其他对象有最少的了解,只与直接的朋友通信。
1/* 违反LoD:订单服务深入访问多层对象 */ 2class OrderService { 3 constructor(userService) { 4 this.userService = userService 5 } 6 7 getOrderSummary(orderId) { 8 const order = this.userService.orderRepository.findOrder(orderId) 9 const user = order.getUser() 10 const address = user.getAddress() // 违反:访问间接对象 11 const city = address.getCity() // 违反:访问更深层对象 12 return {city} 13 } 14} 15 16// 使用 - 耦合度过高 17const service = new OrderService(userService) 18const summary = service.getOrderSummary(123) 19/* 违反LoD结束 */ 20 21/* 遵循LoD:只与直接朋友通信,减少耦合 */ 22class OrderRepository { 23 getOrderSummary(orderId) { 24 const order = this.findOrder(orderId) 25 return order.getSummary() // 委托给订单对象处理内部细节 26 } 27} 28 29class OrderService { 30 constructor(orderRepository) { 31 this.orderRepository = orderRepository 32 } 33 34 getOrderSummary(orderId) { 35 const orderSummary = this.orderRepository.getOrderSummary(orderId) 36 return { 37 orderId: orderSummary.id, 38 userName: orderSummary.userName, 39 city: orderSummary.city 40 } // 遵循:只访问直接提供的数据 41 } 42} 43 44// 使用 - 耦合度低,易于测试和维护 45const orderRepo = new OrderRepository() 46const service = new OrderService(orderRepo) 47const summary = service.getOrderSummary(123) 48console.log(summary.city) 49/* 遵循LoD结束 */ 50
最后
回到前言中的问题:
如何确保新的管理员用户类型能够兼容普通用户?
答案就是应用里氏替换原则。
确保子类完全遵守父类的行为契约,任何使用父类的地方都可以安全地替换为子类,不会破坏系统功能!
《设计模式的原则有哪些?》 是转载文章,点击查看原文。
