七大设计原则

在软件开发的领域中,随着技术的不断进步和市场需求的不断变化,软件系统的设计和维护变得越来越重要。为了确保软件系统能够长期有效地运行,并且能够在未来的发展中适应新的需求和技术变化,提高软件系统的可维护性和可复用性成为了开发过程中的关键目标。此外,增加软件的可扩展性和灵活性也至关重要,因为它们可以确保软件系统能够轻松地添加新功能或者修改现有功能,而不会引发大量的结构重组或代码重写。

为了实现这些目标,程序员在开发程序时应该遵循一系列的设计原则。其中,有7条原则被广泛认为是提高软件开发效率、节约软件开发成本和维护成本的关键。
在这里插入图片描述

开闭原则

对扩展开放,对修改关闭。在程序需要进行拓展的时候,不需要去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。以下代码中ShoppingCart 类是对支付方式这一变化点开放的(可以轻易地添加新的 PaymentStrategy 实现),但对修改是关闭的(不需要为了支持新的支付方式而去修改 ShoppingCart 类的源代码)。
定义一个支付方式策略:

interface PaymentStrategy {
    void pay(double amount);
}

// Step 2: 创建具体实现类,实现支付策略的扩展
class CashPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using cash.");
    }
}

class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using credit card.");
    }
}

定义一个使用支付策略的上下文类,它对支付方式进行抽象:

class ShoppingCart {
    private List<Item> items;
    private PaymentStrategy paymentStrategy;

    public ShoppingCart(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout() {
        double total = calculateTotal();
        paymentStrategy.pay(total); // 这里使用了多态,无需修改ShoppingCart就能添加新的支付方式
    }

    private double calculateTotal() {
        // 计算购物车总价的逻辑...
        return 100.0; // 示例金额
    }
}

里氏替换原则

任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

依赖倒转原则

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,相对于细节的多变性,抽象的东西要稳定的多。

// 抽象(接口)
interface MessageSender {
    void sendMessage(String message);
}

// 具体实现(细节)
class EmailSender implements MessageSender {
    @Override
    public void sendMessage(String message) {
        System.out.println("Sending email with message: " + message);
        // 实现发送邮件的逻辑
    }
}

class SMSSender implements MessageSender {
    @Override
    public void sendMessage(String message) {
        System.out.println("Sending SMS with message: " + message);
        // 实现发送短信的逻辑
    }
}

// 高层模块(不依赖具体实现)
class Staff {
    private MessageSender sender;

    // 构造函数通过依赖注入设置消息发送器
    public Staff(MessageSender sender) {
        this.sender = sender;
    }

    public void receiveMessageAndSendNotification(String receivedMessage) {
        String notification = "Received message: " + receivedMessage;
        sender.sendMessage(notification);
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建高层模块实例时,可以选择不同的具体实现
        Staff staff = new Staff(new EmailSender()); // 或者 new SMSSender()
        staff.receiveMessageAndSendNotification("An important notice!");

        // 更改通知方式只需要更换MessageSender的实现即可
        staff = new Staff(new SMSSender());
        staff.receiveMessageAndSendNotification("An urgent alert!");
    }
}

Staff 类是高层模块,它依赖于 MessageSender 接口,而不是具体的发送器实现,这使得 Staff 类可以根据需要灵活切换不同的消息发送方式,而不必更改其自身的代码。这就是依赖倒转原则的体现。

接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP)指出客户端不应该被迫依赖它们不使用的方法。换句话说,每个接口都应该有明确的责任,接口中的方法应该是高内聚的,避免“胖”接口。

// 未遵守ISP的“胖”接口
interface Employee {
    void calculateSalary();
    void printDetails();
    void sendEmailReminder();
    void updateWorkLog();
}

// 遵守ISP,将接口拆分为多个具有单一责任的小接口
interface SalaryCalculator {
    void calculateSalary();
}

interface EmployeeInfoPrinter {
    void printDetails();
}

interface EmailService {
    void sendEmailReminder();
}

interface WorkLogUpdater {
    void updateWorkLog();
}

// 实体类分别实现所需的接口
class Developer implements SalaryCalculator, EmployeeInfoPrinter {
    // 实现calculateSalary和printDetails方法
    // ...
}

class Manager implements SalaryCalculator, EmailService {
    // 实现calculateSalary和sendEmailReminder方法
 }

迪米特法则

如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
不遵守迪米特法则:

class Order {
    private Customer customer;
    
    public Order(Customer customer) {
        this.customer = customer;
    }
    
    // 直接访问Customer的地址信息,违反迪米特法则
    public String getShippingAddressZipCode() {
        return customer.getAddress().getZipCode();
    }
}

class Customer {
    private Address address;
    
    public Customer(Address address) {
        this.address = address;
    }
    
    public Address getAddress() {
        return address;
    }
}

class Address {
    private String zipCode;
    
    public Address(String zipCode) {
        this.zipCode = zipCode;
    }
    
    public String getZipCode() {
        return zipCode;
    }
}

Order 类原本直接访问 Customer 的 Address 对象来获取邮编,这违反了迪米特法则。可以通过在 Customer 类中提供一个方法,Order 类只需与它的直接朋友 Customer 交流,从而降低了耦合度,遵循了迪米特法则。

合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
原则强调了在设计中应尽量使用关联(Association)、组合(Composition)或聚合(Aggregation)关系来组合现有对象形成更复杂的结构或行为,而不是通过创建类层次结构进行功能扩展。
示例:添加日志记录功能
在一个系统中,若要为UserManager类添加日志记录功能,不是通过让UserManager直接继承具有日志功能的类,而是让它包含(组合或聚合)一个Logger对象,通过委托给这个对象来执行日志记录的操作:

public class UserManager {
    private Logger logger;

    public UserManager(Logger logger) {
        this.logger = logger;
    }

    // 其他业务逻辑方法...
    
    public void addUser(User user) {
        // 执行添加用户逻辑
        // ...
        
        // 委托给logger记录操作日志
        logger.log("User added: " + user.getUsername());
    }
}

过这种方式,可以轻易更换不同的Logger实现,而无需更改UserManager的结构,同时也避免了因过度使用继承带来的设计复杂性和维护困难。

单一职责原则

这一原则的核心在于控制类的粒度大小、将对象解耦、提高其内聚性。

  1. 违反单一职责原则的例子
    假设有一个Employee类,它既处理员工的工资计算也负责员工的个人信息管理,Employee类同时承担了员工信息管理和薪资计算两种职责。如果因为绩效计算规则改变需要修改calculateBonus方法,理论上不应影响到员工地址管理这部分功能:
public class Employee {
    private String name;
    private String address;
    private double salary;

    public Employee(String name, String address, double salary) {
        this.name = name;
        this.address = address;
        this.salary = salary;
    }

    // 负责员工个人信息管理
    public void updateAddress(String newAddress) {
        this.address = newAddress;
    }

    // 负责工资计算
    public double calculateBonus(double performanceRating) {
        return salary * (performanceRating / 10);
    }
}
  1. 遵循单一职责原则的例子
    我们将上述职责分开到两个类中,EmployeeInfo类专门负责个人信息管理,而EmployeeSalaryCalculator类则专注于薪资计算。这样,当任何一个职责的需求发生改变时,都不会直接影响到另一个职责的实现,从而提高了代码的可读性和可维护性:
public class EmployeeInfo {
    private String name;
    private String address;

    public EmployeeInfo(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public void updateAddress(String newAddress) {
        this.address = newAddress;
    }
}

public class EmployeeSalaryCalculator {
    private double salary;

    public EmployeeSalaryCalculator(double salary) {
        this.salary = salary;
    }

    public double calculateBonus(double performanceRating) {
        return salary * (performanceRating / 10);
    }
}

// 员工实体可以持有这两个类的引用
public class Employee {
    private EmployeeInfo info;
    private EmployeeSalaryCalculator salaryCalculator;

    public Employee(String name, String address, double salary) {
        this.info = new EmployeeInfo(name, address);
        this.salaryCalculator = new EmployeeSalaryCalculator(salary);
    }

   
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/544825.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习—特征预处理和降维(四)

什么是特征预处理&#xff1f; 通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程 1包含内容 数值型数据的无量纲化&#xff1a; 归一化标准化 2特征预处理API sklearn. preprocessing为什么要进行归一化 or 标准化&#xff1f; 特征的单位或者大小相差较大…

vue源码解析——diff算法/双端比对/patchFlag/最长递增子序列

虚拟dom——virtual dom&#xff0c;提供一种简单js对象去代替复杂的 dom 对象&#xff0c;从而优化 dom 操作。virtual dom 是“解决过多的操作 dom 影响性能”的一种解决方案。virtual dom 很多时候都不是最优的操作&#xff0c;但它具有普适性&#xff0c;在效率、可维护性之…

BackTrader 中文文档(十一)

原文&#xff1a;www.backtrader.com/ 基准测试 原文&#xff1a;www.backtrader.com/docu/observer-benchmark/benchmarking/ 票号 #89 是关于添加对资产的基准测试的。理智的做法是&#xff0c;即使有一种策略&#xff0c;即使是正的&#xff0c;也低于简单跟踪资产将提供的内…

第⑭讲:Ceph集群管理:守护进程管理、日志管理和端口号配置

文章目录 1.Ceph各组件守护进程的管理方式2.守护进程管理操作2.1.Ceph所有组件的守护进程列表2.2.重启当前主机中所有的Ceph组件2.3.重启主机中所有的Monitor组件2.4.重启指定主机的Monitor组件2.5.重启指定的OSD组件 3.Ceph的日志管理4.Ceph集群各组件的守护进程5.Ceph集群各组…

All in One:Prometheus 多实例数据统一管理最佳实践

作者&#xff1a;淡唯&#xff08;啃唯&#xff09;、阳其凯&#xff08;逸陵&#xff09; 引言 Prometheus 作为目前最主流的可观测开源项目之一&#xff0c;已经成为云原生监控的事实标准&#xff0c;被众多企业广泛应用。在使用 Prometheus 的时候&#xff0c;我们经常会遇…

自然语言处理: 第二十六章大模型基底之Mistral 8x7B

文章地址: 2401.04088.pdf (arxiv.org) 项目地址: mistralai/mistral-src: Reference implementation of Mistral AI 7B v0.1 model 前言: 本文意在一文深度剖析Mistral 8X7B的关键改进点。 Mistral AI是一个由DeepMind和Meta的三位前员工在巴黎共同创立的AI公司。其在23年…

tsconfig.json文件常用配置

最近在学ts&#xff0c;因为tsconfig的配置实在太多啦&#xff0c;所以写此文章用作记录&#xff0c;也作分享 作用&#xff1f; tsconfig.jsono是ts编译器的配置文件&#xff0c;ts编译器可以根据它的信息来对代码进行编译 初始化一个tsconfig文件 tsc -init配置参数解释 …

股票开户佣金最低多少?万一!A股开户多少钱合适?

开户佣金 通常情况下&#xff0c;股票开户佣金只要在达成交易的前提才收手续的费用&#xff0c;即买入和卖出的时候。目前&#xff0c;国规定收取最高佣金的比例为千分之三。 也就是说&#xff0c;最高为成交金额的3%&#xff0c;一般都会小于这个比例。最低交易佣金是5元起&a…

mac中的VirtualBox不能分配USB设备到虚拟电脑

mac中的VirtualBox不能分配USB设备到虚拟电脑 检查工具-> 扩展包是否安装 Oracle_VM_VirtualBox_Extension_Pack-7.0.14.vbox-extpack检查usb设备是否打开 检查权限 允许VirtualBox访问&#xff1a;在“安全性与隐私”窗口中&#xff0c;选择“隐私”标签。 在左侧的列表中…

微信过期文件怎么恢复?四个高招助你轻松解决(2024新版)

“微信的文件未下载的情况下过期了&#xff0c;平时微信也没有登录在电脑上&#xff0c;之前也没有进行过数据备份&#xff0c;如何找回这个文件啊&#xff01;&#xff01;感谢回答&#xff01;” “急求&#xff0c;当时忘记点击下载&#xff0c;现在急用微信文件下载不了&a…

Go gin框架(详细版)

目录 0. 为什么会有Go 1. 环境搭建 2. 单-请求&&返回-样例 3. RESTful API 3.1 首先什么是RESTful API 3.2 Gin框架支持RESTful API的开发 4. 返回前端代码 go.main index.html 5. 添加静态文件 main.go 改动的地方 index.html 改动的地方 style.css 改动…

【Linux网络编程】TCP协议

TCP协议 1.TCP协议段格式4位首位长度序号和确认序号16位窗口大小6个标志位 2.确认应答机制3.超时重传机制4.连接管理机制如何理解连接如何理解三次握手如何理解四次挥手 5.流量控制6.滑动窗口7.拥塞控制8.延迟应答9.捎带应答10.面向字节流11.粘包问题12.TCP异常情况13.TCP小结1…

通讯录的实现(单链表版本)

我们首先要知道通讯录的实现是基于单链表的基础上的&#xff0c;所以我们首先要搞懂单链表。&#xff08;注意&#xff1a;今天的代码量较多&#xff09;&#xff0c;但这不是阻挡我们前进的脚步&#xff0c;冲冲冲&#xff01;&#xff01;&#xff01; 单链表的简要概述 我们…

剖析 SPI 在 Spring 中的应用

一、概述 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是Java内置的一种服务提供发现机制&#xff0c;可以用来提高框架的扩展性&#xff0c;主要用于框架的开发中&#xff0c;比如Dubbo&#xff0c;不同框架中实现略有差异&#xff0c;但核心机制相同…

构建第一个ArkTS应用之stateStyles:多态样式

Styles和Extend仅仅应用于静态页面的样式复用&#xff0c;stateStyles可以依据组件的内部状态的不同&#xff0c;快速设置不同样式。这就是我们本章要介绍的内容stateStyles&#xff08;又称为&#xff1a;多态样式&#xff09;。 概述 stateStyles是属性方法&#xff0c;可以…

如何发布自己的Python库?

Python包发布 1、背景概述2、操作指南 1、背景概述 为什么我们要发布自己的Python库&#xff1f;如果你想让你的Python代码&#xff0c;通过pip install xxx的方式供所有人下载&#xff0c;那就需要将代码上传到PyPi上&#xff0c;这样才能让所有人使用 那么&#xff0c;如何发…

【最新整理】3ds Max 大佬都在用的10款爆火插件推荐!

在3D建模和渲染领域&#xff0c;熟悉使用各种插件已经成为了大佬们的标配&#xff0c;而3ds Max作为最受欢迎的三维建模软件之一&#xff0c;更是有着丰富的插件资源。今天&#xff0c;小编将为大家盘点一下最新整理的10款爆火插件&#xff0c;这些插件不仅能够提升你的工作效率…

集合体系java

Collection:单列集合&#xff1a;每个元素只包含一个值 Collection集合存储的是地址 Collection的三种遍历方法如下 //迭代器是用来遍历集合的专用方式&#xff08;数组没有迭代器&#xff09;&#xff0c;在java中迭代器的代表是Iterator //boolean hasNext():询问当前位置…

10万字208道Java经典面试题总结(2024修订版)- SSM篇

&#x1f345; 作者简介&#xff1a;哪吒&#xff0c;CSDN2021博客之星亚军&#x1f3c6;、新星计划导师✌、博客专家&#x1f4aa; &#x1f345; 哪吒多年工作总结&#xff1a;Java学习路线总结&#xff0c;搬砖工逆袭Java架构师 &#x1f345; 技术交流&#xff1a;定期更新…

(三)C++自制植物大战僵尸游戏项目结构说明

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/ErelL 一、项目结构 打开项目后&#xff0c;在解决方案管理器中有五个项目&#xff0c;分别是libbox2d、libcocos2d、librecast、libSpine、PlantsVsZombies五个项目&#xff0c;除PlantsVsZombies外&#xff0c;其他四个…