Java中的七种设计原则

news2024/11/28 21:39:22

1.开闭原则

对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,要去实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。

下面是输入法设置皮肤的例子:

// 抽象皮肤接口
public interface Skin {
    // 显示的方法
    void display();
}
// 默认皮肤类
public class DefaultSkin implements Skin {
    @Override
    public void display() {
        System.out.println("默认皮肤");
    }
}
// 下班了皮肤
public class OffWorkSkin implements Skin{
    @Override
    public void display() {
        System.out.println("下班了皮肤");
    }
}
// 输入法类
@Data
public class Input {
    // 设置皮肤
    private Skin skin;
    public void display() {
        skin.display();
    }
}
    // 测试
    public static void main(String[] args){
        // 创建输入法对象
        Input input = new Input();
        // 创建皮肤对象
        Skin defaultSkin = new DefaultSkin();
        // 设置默认皮肤
        input.setSkin(defaultSkin);
        // 显示皮肤
        input.display(); // 默认皮肤
        // 换成下班了皮肤
        Skin offWorkSkin = new OffWorkSkin();
        input.setSkin(offWorkSkin);
        input.display();// 下班了皮肤
    }

2.单一职责原则

就一个类而言,应该仅有一个引起变化的原因。应该只有一个职责。

说白了就是,一个类或者一个方法就应该干一件事情。

下面是修改用户的例子:

public class UpdateUser {
    // 不符合单一职责原则
    public void updateUser(String type, String oldPassword, String newPassword, String oldUserName, String newUserName) {
        if ("修改密码".equals(type)) {
            System.out.println(oldPassword + "修改密码为:" + newPassword);
        } else if ("修改账号".equals(type)) {
            System.out.println(oldUserName + "修改账号" + newUserName);
        }
    }
    // 符合单一职责原则
    public void updatePassword(String oldPassword, String newPassword) {
        System.out.println(oldPassword + "修改密码为:" + newPassword);
    }
    public void updateUserName(String oldUserName, String newUserName) {
        System.out.println(oldUserName + "修改账号" + newUserName);
    }
}

可以看到上者是根据操作类型进行区分, 不同类型执行不同的逻辑,把修改账号和修改密码这两件事耦合在一起了,如果客户端在操作的时候传错了类型, 那么就会发生错误;

下者则把修改账号和修改密码逻辑分开,各自执行各自的职责,互不干扰,功能清晰明了,符合单一职责原则。

3.依赖倒置原则

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单来说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

下面是一个组装电脑的例子(简单举例电脑只保留CPU吧,嘻嘻嘻):

// Intel处理器类
public class IntelCpu {
    public void getCpu() {
        System.out.println("使用Intel处理器");
    }
}
// 电脑
@Data
public class Computer {
    private IntelCpu intelCpu; // CPU
    public void run() {
        intelCpu.getCpu();
    }
}
    // 测试
    public static void main(String[] args){
        // 创建电脑对象
        Computer computer = new Computer();
        // 设置CPU
        computer.setIntelCpu(new IntelCpu());
        computer.run(); // 使用Intel处理器
    }

可以看到上面的组装的电脑只能使用Intel的CPU,假如我想换成AMD的CPU不行。这就是违反了依赖倒置原则。修改后的代码如下:

// CPU类
public interface Cpu {
    void getCpu();
}
// Intel处理器类
public class IntelCpu implements Cpu {
    @Override
    public void getCpu() {
        System.out.println("使用Intel处理器");
    }
}
// Amd处理器类
public class AmdCpu implements Cpu {
    @Override
    public void getCpu() {
        System.out.println("使用Amd处理器");
    }
}
// 电脑
@Data
public class Computer {
    private Cpu cpu;
    public void run() {
        cpu.getCpu();
    }
}
    // 测试
    public static void main(String[] args){
        // 创建电脑对象
        Computer computer = new Computer();
        // 设置CPU为Intel处理器
        computer.setCpu(new IntelCpu());
        computer.run(); // 使用Intel处理器
        // 设置CPU为Amd处理器
        computer.setCpu(new AmdCpu());
        computer.run(); // 使用Amd处理器
    }

此时,当用户需要更换CPU时,只需要创建实现类去实现CPU接口而不需要修改原本的接口代码,这就符合了开闭原则。

4.接口隔离原则

客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。

下面用一个安全门的例子:

现在有A品牌的安全门具有防火防盗功能,于是把防火防盗的功能提取出来一个接口,即:

// 安全门接口
public interface Door {
    //防火
    void fireproof();
    //防水
    void waterproof();
}
// 安全门接口
public interface Door {
    //防火
    void fireproof();
    //防水
    void waterproof();
}

那么假如我们现在又有一个B品牌的安全门,只有防水功能的呢?那么显然是不能直接实现安全门的接口的,那么应该如何改进呢?改进如下:

// 防火功能
public interface FireProof {
    void fireProof();
}
// 防火功能
public interface FireProof {
    void fireProof();
}
// A类门,具有防火防水功能
public class ADoor implements WaterProof, FireProof {
    @Override
    public void fireProof() {
        System.out.println("A品牌安全门防火功能");
    }
    @Override
    public void waterProof() {
        System.out.println("A品牌安全门防水功能");
    }
}// B类安全门
public class BDoor implements WaterProof {
    @Override
    public void waterProof() {
        System.out.println("B品牌安全门防水功能");
    }
}

这样改进以后可以看到,当不同的品牌的安全门具有不同的功能的时,有什么功能就实现什么功能的接口,假如以后还有一个C品牌类的安全门具有防火防水防盗的功能,那么可以加一个防盗的接口,然后C类门去实现防火防水防盗的接口即可。这就是接口隔离原则。

5.迪米特法则

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

当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

下面是一个明星,粉丝,经纪公司的例子:

// 明星类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Star {
    private String name;
}
// 粉丝类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Fans {
    private String name;
}
// 经纪公司类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {
    private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Agent {
    private Star star;
    private Fans fans;
    private Company company;
    //和粉丝见面的方法
    public void meeting(){
        System.out.println(star.getName() + "和粉丝"+fans.getName() + "见面");
    }
    //和媒体公司洽谈的方法
    public void business(){
        System.out.println(star.getName() + "和" + company.getName() + "洽谈");
    }
}
    // 测试
    public static void main(String[] args) {
        //创建明星对象
        Star star = new Star("严晓波");
        //创建粉丝对象
        Fans fans = new Fans("彭晓锋");
        //创建公司对象
        Company company = new Company("杨永信电疗娱乐有限公司");
        //创建经纪人对象
        Agent agent = new Agent(star, fans, company);
        agent.meeting();// 严晓波和粉丝彭晓锋见面
        agent.business();// 严晓波和杨永信电疗娱乐有限公司洽谈
    }

这里明星的日常事务有经纪人负责处理,比如和粉丝见面,和媒体公司洽谈,这里明星的朋友是经纪人,而和粉丝和公司是陌生人,所以适合使用迪米特法则。本例子可知迪米特法则主要是为了降低明星与粉丝和公司之间的耦合度。

6.里氏替换原则

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

下面正方形不是长方形的例子:

// 长方形
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Rectangle {
    private Double length;
    private Double width;
}
// 正方形类(错误继承长方形)
public class Square extends Rectangle {
    @Override
    public void setLength(Double length) {
        super.setLength(length);
        super.setWidth(length);
    }
    public void setWidth(Double width) {
        super.setLength(width);
        super.setWidth(width);
    }
}

// 测试类
public class Test {
    public static void main(String[] args){
        // 创建长方形对象
        Rectangle rectangle = new Rectangle(15.0, 10.0);
        // 扩宽
        resize(rectangle);
        // 15.0
        // 16.0
        print(rectangle);
        System.out.println("====================");
        Square square = new Square();
        square.setLength(10.0);
        // 扩宽
        resize(square);
        // 死循环 直到oom
        print(square);
    }
    // 扩宽修正方法
    public static void resize(Rectangle rectangle) {
        while (rectangle.getWidth() <= rectangle.getLength()) {
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }
    // 打印长宽
    public static void print(Rectangle rectangle) {
        System.out.println(rectangle.getLength());
        System.out.println(rectangle.getWidth());
    }
}

可以从运行上面的代码看到,当我们调用resize()方法时候,普通长方形可以正常运行;但是当调用resize()方法的对象是正方形时,会死循环,这是因为正方形的长宽相等,永远都不会满足扩宽的条件。所以可以得出结论:在resize()方法中,长方形的参数是不能被正方形的参数所替代的,如果进行了替换就得不到预期的效果,所以Square和Rectangle类之间的继承关系违反了里氏代换原则,它们之间的继承关系不成立。修改后的代码如下:

// 四边形接口,长方形和正方形同属于四边形
public interface Quadrilateral {
    Double getLength(); // 获取长
    Double getWidth(); // 获取宽
}
// 长方形
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Rectangle implements Quadrilateral {
    private Double length;
    private Double width;
}
@Data
public class Square implements Quadrilateral {
    private Double side;
    @Override
    public Double getLength() {
        return side;
    }
    @Override
    public Double getWidth() {
        return side;
    }
}
// 测试类
public class Test {
    public static void main(String[] args){
        // 创建长方形对象
        Rectangle rectangle = new Rectangle(15.0, 10.0);
        // 扩宽
        resize(rectangle);
        // 15.0
        // 16.0
        print(rectangle);
    }
    // 扩宽修正方法
    public static void resize(Rectangle rectangle) {
        while (rectangle.getWidth() <= rectangle.getLength()) {
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }
    // 打印长宽
    public static void print(Rectangle rectangle) {
        System.out.println(rectangle.getLength());
        System.out.println(rectangle.getWidth());
    }
}

这样Square类的对象就不能使用resize()方法。

7.合成复用原则

合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

通常类的复用分为继承复用和合成复用两种。

继承复用虽然有简单和易实现的优点,但它也有以下缺点:

  • 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称“白箱”复用。
  • 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展和维护。
  • 它限制了复用的灵活性。从父类继承而来的实现时静态的,在编译时已经定义,所以运行时不可能发生变化。

采用组合或聚合复用,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:

  • 它维持了类的封装性。因为成员对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
  • 对象间的耦合度低。可以在类的成员位置声明抽象。
  • 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的对象。

下面是汽车种类的例子:

继承复用
继承复用

合成复用

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux入门---缓冲区模拟实现

前言 有了前面的基础我们知道c语言的缓冲区本质上就是FILE结构体的一部分&#xff0c;我们每次使用stdout将数据打印到屏幕上时&#xff0c;本质上是先将数据拷贝到FILE结构体的缓冲区中&#xff0c;然后再拷贝到内核缓冲区中也就是file结构体里面的缓冲区&#xff0c;最后再刷…

TSN网络流量记录器:一种经济高效的解决方案,用于验证汽车网络中的以太网融合。

在未来几年&#xff0c;汽车线束将从不同协议的异构网络转变为分层的同构以太网网络。在这种新情况下&#xff0c;模拟真实车辆网络的实验室测试台将需要分析工具以支持它们在车内通信验证过程中进行验证。 汽车向以太网融合原因 随着汽车内部技术变得越来越复杂&#xff0c;相…

信息调查的观念

每次做一件事前都要把这件事调查清楚&#xff0c;比如考一门科目我们要把和这门科目有关的资源都收集起来&#xff0c;然后把再从中筛选出有用的信息&#xff0c;如数值计算方法我们在考试前就可以把b站有关的学习资源网课或者前人总结的考试经验做个收集总结&#xff0c;做出对…

正则表达式 - 量词

目录 一、贪心、懒惰和占有 二、用 *、 和 ? 进行匹配 三、匹配特定次数 四、懒惰量词 五、占有量词 六、示例——括号字符串计数 {m,n} 是通用形式的量词&#xff0c;正则表达式还有三个常用量词&#xff0c;分别是 、?、*。它们的形态虽然不同于 {m,n}&#xff0c;功…

8年测试总结,项目/团队如何做自动化测试?效率价值?吐血整理...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

Linux:centos:用户基础设置》》添加,修改属性,删除,修改密码

useradd &#xff08;属性&#xff09; 用户 新建用户 usermod &#xff08;属性&#xff09; 用户 调整用户属性 userdel &#xff08;属性&#xff09; 用户 删除用户 passwd &#xff08;属性&#xff09; 用户 修改用户密…

科技云报道:国内AI大模型鏖战,上演科技罗生门

科技云报道原创。 ChatGPT的狂热从年初持续至今&#xff0c;这份狂热不仅仅来源于用户层&#xff0c;从业者、投资人以及企业可以说有过之无不及。 于是&#xff0c;这些投资人、从业者以及企业将狂热转化&#xff0c;宣布入局大模型赛道并推出相关产品。一时间&#xff0c;大…

百万年薪架构师甩出的SpringBoot趣味实战手册,GitHub标星81.6K

前言 本书内容很全面&#xff0c;囊括了Spring生态的常用技术&#xff0c;如MVC、持久化、 Redis、定时任务、消息队列、搜索引擎。本书知识讲解由浅到深&#xff0c;循序渐进&#xff0c;从Hello World讲到Spring核心原理&#xff1b;技术讲解深入浅出&#xff0c;总能以“接…

无线传感网络的节点部署覆盖及能源消耗问题研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 随着微电子技术的不断发展,被称作传感器节点的微小智能嵌入式设备不断的被开发出来,推动了无线传感器网络的发展。一个典型的无…

Matlab Python 如何在figure上画出表格【优化】

之前写过一次博客 Matlab&Python 如何在figure上画出表格 这次是对该博客的优化 图的结果见下相对之前有很大的进步&#xff1a; coding 在这里包含数据的绘制&#xff0c;表格的添加&#xff0c;设置表格的大小、位置等 clc clear close all; path(path,E:\new_matlab_Too…

如何获取不同分区模板的基因表达矩阵,abagen: Allen 大脑图谱遗传数据工具箱的使用笔记

abagen: Allen 大脑图谱遗传数据工具箱的使用笔记 介绍使用abagen工具箱进行标准化处理和报告代码实例——获取Schaefer2018_400Parcels_7Networks的基因表达数据基于surf空间的模板基于volume空间的模板参考文献介绍 基因表达从根本上塑造了人类大脑的结构和功能结构。像Allen…

【Linux】shell编程—数组

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、shell数组1,数组的概念2.数组的定义 二、Shell数组操作1. 获取数组的所有元素的列表2. 获取数组的所有元素下标3.取数组的元素个数4. 获取数组的某个元素的值5.…

Jmeter 压测 QPS

文章目录 1、准备工作1.1 Jmeter的基本概念1.2 Jmeter的作用1.3.Windows下Jmeter下载安装1.4 Jmeter的目录结构1.5 启动1.6 设置中文1.6.1 设置调整1.6.2 配置文件调整&#xff08;一劳永逸&#xff09; 2、Jmeter线程组基本操作2.1 线程组是什么2.2 线程组2.2.1 创建线程组2.2…

原型part学习NeurIPS2019

当我们面临具有挑战性的图像分类任务时&#xff0c;我们希望通过分解part来解释推理。每一类别的更多原型证据有助于做出最终分类决策。作者提出一种深度网络架构&#xff1a;Prototypical Part网络即ProtoPNet。网络通过寻找原型part来解释图像&#xff0c;并基于原型part进行…

同步 Swagger URL问题, 用这个插件就可解决

这个开源的 API 管理工具叫 Postcat, 支持从 Swagger URL 增量同步 API 数据到 Postcat。 使用 进入 API 模块&#xff0c;鼠标移动到主按钮加号&#xff0c;下拉看到从 Swagger 同步 URL 的选项。 填写完配置点击立即同步即可同步 API 数据。 同步规则 新的数据覆盖旧的数据…

PHP语言调用api接口,电商平台商品详情接口(封装可高并发)

PHP是为Web而生的语言&#xff0c;它提供了一些强大的内置函数来处理HTTP请求和响应。PHP为开发人员提供了一些Web开发工具&#xff0c;包括HTML、CSS、JavaScript以及各种数据库的连接和互动。与其他Web开发工具相比&#xff0c;PHP可以更加高效地运转与发挥作用。 PHP表现出…

Matlab 非线性迭代法(3)阻尼牛顿法 L-M

高斯牛顿法详解_我只是一只自动小青蛙的博客-CSDN博客 一、思想 先看一下牛顿高斯迭代法的缺点&#xff1a; 1、在计算的过程中可能会出现奇异矩阵&#xff08;不满秩&#xff09;&#xff0c;比如&#xff1a;J(k)​)TJ(k) 为病态矩阵的时候就不能得到正确的解&#xff0c;或…

如何提升性能测试效能

上周六应邀在天津devops峰会的质量内建专场做了一次分享&#xff0c;主题是《稳定性保障利器&#xff1a;全链路压测》。 其中关于全链路压测对质量内建的意义&#xff0c;我做了一个总结&#xff0c;如下图所示。本文基于下图做了展开描述&#xff0c;仅供参考。 如何理解性能…

从零开始Vue3+Element Plus后台管理系统(八)——模仿禅道做一个Vue3版本的高级查询组件

暗黑模式 使用 Vue3element Plus 简单模仿了禅道系统的高级搜索组件&#xff0c;说简单也有点复杂&#xff0c;还没有完全开发完&#xff0c;但是大体架子有了&#xff0c;剩下一些功能点继续coding。边开发边记录吧&#xff0c;因为这个相比之前的内容确实复杂一些&#xff0c…

Java的基操,基操(一)

&#x1f525;二进制&#x1f525;二进制和十进制的转化&#x1f525;注释&#x1f525;标识符&#x1f525;关键字/保留字&#x1f525;变量(variable) &#x1f525;二进制 二进制&#xff0c;是计算技术中广泛采用的一种数制&#xff0c;由德国数理哲学大师莱布尼茨于 1679 …