设计模式的原则有哪些?

前言

温馨提示

对于原本不太熟悉设计模式的人来说(比如在下),这些内容是需要一定的时间消化的!慢慢来 😆

👋 你好啊,我是你的人类朋友!

今天说说设计模式的原则有哪些!

在开发用户权限系统时,你是否遇到过这样的问题:

当创建新的管理员用户类型时,发现它无法兼容普通用户的所有方法,导致系统中到处需要判断用户类型?

让我们了解设计模式的基本原则,构建更健壮的软件架构~

健壮是啥意思? 健壮是指软件系统在面对变化和复杂性时,能够保持稳定运行的能力。也就是耐造的能力。

正文

SOLID 原则

SOLID 是面向对象设计的五个基本原则。

1. 单一职责原则 (SRP - Single Responsibility Principle)

含义:一个类应该只有一个引起变化的原因。

javascript 复制代码
/* 违反SRP:订单处理类承担了验证、计算、存储、通知多个职责 */
class OrderProcessor {
    validateOrder(order) {
        return order.items.length > 0
    }

    calculateTotal(order) {
        return order.items.reduce((sum, item) => sum + item.price, 0)
    }

    saveToDatabase(order) {
        console.log('保存订单到数据库')
    }

    sendEmailConfirmation(order) {
        console.log('发送订单确认邮件')
    }
}

// 使用
const processor = new OrderProcessor()
processor.validateOrder({items: [{price: 100}]})
/* 违反SRP结束 */

/* 遵循SRP:每个类只负责一个明确职责 */
class OrderValidator {
    validate(order) {
        return order.items.length > 0
    }
}

class OrderCalculator {
    calculateTotal(order) {
        return order.items.reduce((sum, item) => sum + item.price, 0)
    }
}

class OrderRepository {
    save(order) {
        console.log('保存订单到数据库')
    }
}

class OrderProcessor {
    constructor() {
        this.validator = new OrderValidator()
        this.calculator = new OrderCalculator()
        this.repository = new OrderRepository()
    }

    process(order) {
        if (!this.validator.validate(order)) return
        order.total = this.calculator.calculateTotal(order)
        this.repository.save(order)
    }
}

// 使用
const order = {items: [{price: 100}, {price: 200}]}
const processor = new OrderProcessor()
processor.process(order)
console.log(order.total) // 输出: 300
/* 遵循SRP结束 */

2. 开闭原则 (OCP - Open-Closed Principle)

含义:软件实体应该对扩展开放,对修改关闭。

javascript 复制代码
/* 违反OCP:添加新通知类型需要修改原有类 */
class NotificationService {
    send(message, type) {
        if (type === 'email') {
            console.log(`发送邮件: ${message}`)
        } else if (type === 'sms') {
            console.log(`发送短信: ${message}`)
        }
        // 添加新类型需要修改这里
    }
}

// 使用
const notifier = new NotificationService()
notifier.send('订单创建成功', 'email')
/* 违反OCP结束 */

/* 遵循OCP:通过扩展而非修改来支持新类型 */
class Notification {
    send(message) {
        throw new Error('必须实现send方法')
    }
}

class EmailNotification extends Notification {
    send(message) {
        console.log(`发送邮件: ${message}`)
    }
}

class SMSNotification extends Notification {
    send(message) {
        console.log(`发送短信: ${message}`)
    }
}

class PushNotification extends Notification {
    send(message) {
        console.log(`发送推送: ${message}`)
    }
}

class NotificationService {
    constructor() {
        this.notifications = []
    }

    addNotification(notification) {
        this.notifications.push(notification)
    }

    broadcast(message) {
        this.notifications.forEach(notification => {
            notification.send(message)
        })
    }
}

// 使用
const service = new NotificationService()
service.addNotification(new EmailNotification())
service.addNotification(new SMSNotification())
service.broadcast('系统维护通知')
// 输出: 发送邮件: 系统维护通知
// 输出: 发送短信: 系统维护通知
/* 遵循OCP结束 */

3. 里氏替换原则 (LSP - Liskov Substitution Principle)

含义:子类应该能够替换它们的父类,而不影响程序的正确性。

javascript 复制代码
/* 违反LSP:子类改变了父类的权限检查逻辑 */
class User {
    constructor(permissions) {
        this.permissions = permissions || []
    }

    hasPermission(permission) {
        return this.permissions.includes(permission)
    }

    canDeleteContent() {
        return this.hasPermission('delete')
    }
}

class AdminUser extends User {
    canDeleteContent() {
        // 违反:管理员绕过权限检查,破坏了父类契约
        return true
    }
}

// 使用 - 会出现不一致行为
function deleteContent(user, content) {
    if (user.canDeleteContent()) {
        console.log(`删除内容: ${content}`)
    }
}

const regularUser = new User(['read'])
const adminUser = new AdminUser([]) // 没有delete权限但可以删除
deleteContent(regularUser, '文章') // 无输出
deleteContent(adminUser, '文章') // 输出: 删除内容: 文章
/* 违反LSP结束 */

/* 遵循LSP:子类遵守父类的行为契约 */
class User {
    constructor(permissions) {
        this.permissions = permissions || []
    }

    hasPermission(permission) {
        return this.permissions.includes(permission)
    }

    canDeleteContent() {
        return this.hasPermission('delete')
    }
}

class AdminUser extends User {
    canDeleteContent() {
        // 遵循:仍然进行权限检查,只是权限范围更大
        return this.hasPermission('delete') || this.hasPermission('admin_delete')
    }
}

// 使用 - 行为一致
const regularUser = new User(['delete'])
const adminUser = new AdminUser(['admin_delete'])
deleteContent(regularUser, '文章') // 输出: 删除内容: 文章
deleteContent(adminUser, '文章') // 输出: 删除内容: 文章
/* 遵循LSP结束 */

4. 接口隔离原则 (ISP - Interface Segregation Principle)

含义:客户端不应该被迫依赖它们不使用的接口。

javascript 复制代码
/* 违反ISP:简单商店被迫依赖所有支付方法 */
class PaymentProcessor {
    processCreditCard(amount) {
        console.log(`信用卡支付: $${amount}`)
    }
    processPayPal(amount) {
        console.log(`PayPal支付: $${amount}`)
    }
    processBankTransfer(amount) {
        console.log(`银行转账: $${amount}`)
    }
}

class SimpleStore {
    constructor() {
        this.paymentProcessor = new PaymentProcessor()
    }

    processPayment(amount) {
        // 只使用信用卡,但依赖了所有支付方法
        this.paymentProcessor.processCreditCard(amount)
    }
}

// 使用
const store = new SimpleStore()
store.processPayment(100) // 输出: 信用卡支付: $100
/* 违反ISP结束 */

/* 遵循ISP:客户端只依赖需要的接口 */
class PaymentProcessor {
    process(amount) {
        throw new Error('必须实现process方法')
    }
}

class CreditCardProcessor extends PaymentProcessor {
    process(amount) {
        console.log(`信用卡支付: $${amount}`)
    }
}

class PayPalProcessor extends PaymentProcessor {
    process(amount) {
        console.log(`PayPal支付: $${amount}`)
    }
}

class SimpleStore {
    constructor(paymentProcessor) {
        this.paymentProcessor = paymentProcessor
    }

    processPayment(amount) {
        this.paymentProcessor.process(amount)
    }
}

// 使用
const creditStore = new SimpleStore(new CreditCardProcessor())
const paypalStore = new SimpleStore(new PayPalProcessor())
creditStore.processPayment(100) // 输出: 信用卡支付: $100
paypalStore.processPayment(200) // 输出: PayPal支付: $200
/* 遵循ISP结束 */

5. 依赖倒置原则 (DIP - Dependency Inversion Principle)

含义:高层模块不应该依赖低层模块,两者都应该依赖抽象。

javascript 复制代码
/* 违反DIP:服务层直接依赖具体的数据访问实现 */
class MySQLUserRepository {
    findUserById(id) {
        console.log(`从MySQL查询用户: ${id}`)
        return {id, name: 'MySQL用户'}
    }
}

class UserService {
    constructor() {
        // 违反:直接依赖具体实现
        this.userRepository = new MySQLUserRepository()
    }

    getUserProfile(id) {
        return this.userRepository.findUserById(id)
    }
}

// 使用
const service = new UserService()
const user = service.getUserProfile(1)
console.log(user.name) // 输出: MySQL用户
/* 违反DIP结束 */

/* 遵循DIP:通过抽象解耦依赖关系 */
class UserRepository {
    findUserById(id) {
        throw new Error('必须实现findUserById方法')
    }
}

class MySQLUserRepository extends UserRepository {
    findUserById(id) {
        console.log(`从MySQL查询用户: ${id}`)
        return {id, name: 'MySQL用户'}
    }
}

class MongoDBUserRepository extends UserRepository {
    findUserById(id) {
        console.log(`从MongoDB查询用户: ${id}`)
        return {id, name: 'MongoDB用户'}
    }
}

class UserService {
    constructor(userRepository) {
        // 遵循:依赖抽象接口
        this.userRepository = userRepository
    }

    getUserProfile(id) {
        return this.userRepository.findUserById(id)
    }
}

// 使用 - 可以轻松切换不同的数据源
const mysqlService = new UserService(new MySQLUserRepository())
const mongoService = new UserService(new MongoDBUserRepository())
mysqlService.getUserProfile(1) // 输出: 从MySQL查询用户: 1
mongoService.getUserProfile(1) // 输出: 从MongoDB查询用户: 1
/* 遵循DIP结束 */

其他重要原则

6. 合成复用原则 (CRP - Composite Reuse Principle)

含义:优先使用对象组合,而不是继承来达到复用的目的。

javascript 复制代码
/* 违反CRP:使用继承导致类层次复杂 */
class Logger {
    log(message) {
        console.log(`基础日志: ${message}`)
    }
}

class FileLogger extends Logger {
    log(message) {
        super.log(message)
        console.log(`文件日志: ${message}`)
    }
}

// 使用
const fileLogger = new FileLogger()
fileLogger.log('测试消息')
/* 违反CRP结束 */

/* 遵循CRP:使用组合提高灵活性 */
class ConsoleWriter {
    write(message) {
        console.log(`控制台: ${message}`)
    }
}

class FileWriter {
    write(message) {
        console.log(`写入文件: ${message}`)
    }
}

class DatabaseWriter {
    write(message) {
        console.log(`保存到数据库: ${message}`)
    }
}

class Logger {
    constructor(writer) {
        this.writer = writer
    }

    log(message) {
        this.writer.write(message)
    }
}

// 使用 - 可以灵活组合不同的写入器
const consoleLogger = new Logger(new ConsoleWriter())
const fileLogger = new Logger(new FileWriter())
const dbLogger = new Logger(new DatabaseWriter())

consoleLogger.log('控制台消息') // 输出: 控制台: 控制台消息
fileLogger.log('文件消息') // 输出: 写入文件: 文件消息
dbLogger.log('数据库消息') // 输出: 保存到数据库: 数据库消息
/* 遵循CRP结束 */

7. 迪米特法则 (LoD - Law of Demeter)

含义:一个对象应该对其他对象有最少的了解,只与直接的朋友通信。

javascript 复制代码
/* 违反LoD:订单服务深入访问多层对象 */
class OrderService {
    constructor(userService) {
        this.userService = userService
    }

    getOrderSummary(orderId) {
        const order = this.userService.orderRepository.findOrder(orderId)
        const user = order.getUser()
        const address = user.getAddress() // 违反:访问间接对象
        const city = address.getCity() // 违反:访问更深层对象
        return {city}
    }
}

// 使用 - 耦合度过高
const service = new OrderService(userService)
const summary = service.getOrderSummary(123)
/* 违反LoD结束 */

/* 遵循LoD:只与直接朋友通信,减少耦合 */
class OrderRepository {
    getOrderSummary(orderId) {
        const order = this.findOrder(orderId)
        return order.getSummary() // 委托给订单对象处理内部细节
    }
}

class OrderService {
    constructor(orderRepository) {
        this.orderRepository = orderRepository
    }

    getOrderSummary(orderId) {
        const orderSummary = this.orderRepository.getOrderSummary(orderId)
        return {
            orderId: orderSummary.id,
            userName: orderSummary.userName,
            city: orderSummary.city
        } // 遵循:只访问直接提供的数据
    }
}

// 使用 - 耦合度低,易于测试和维护
const orderRepo = new OrderRepository()
const service = new OrderService(orderRepo)
const summary = service.getOrderSummary(123)
console.log(summary.city)
/* 遵循LoD结束 */

最后

回到前言中的问题:

如何确保新的管理员用户类型能够兼容普通用户?

答案就是应用里氏替换原则

确保子类完全遵守父类的行为契约,任何使用父类的地方都可以安全地替换为子类,不会破坏系统功能!

相关推荐
程序员小凯3 小时前
Spring Boot文件处理与存储详解
java·spring boot·后端
!执行4 小时前
Web3 前端与合约交互
前端·web3·1024程序员节
潘小安4 小时前
跟着 AI 学(二)- Quill 接入速通
前端
十里-4 小时前
在 Vue2 中为 Element-UI 的 el-dialog 添加拖拽功能
前端·vue.js·ui
shada4 小时前
从Google Chrome商店下载CRX文件
前端·chrome
左耳咚4 小时前
项目开发中从补码到精度丢失的陷阱
前端·javascript·面试
黑云压城After4 小时前
vue2实现图片自定义裁剪功能(uniapp)
java·前端·javascript
芙蓉王真的好14 小时前
NestJS API 提示信息规范:让日志与前端提示保持一致的方法
前端·状态模式
dwedwswd5 小时前
技术速递|从 0 到 1:用 Playwright MCP 搭配 GitHub Copilot 搭建 Web 应用调试环境
前端·github·copilot