【设计模式】六大原则详解,每个原则提供代码示例

设计模式六大原则

目录

  • 一、单一职责原则——SRP
    • 1、作用
    • 2、基本要点
    • 3、举例
  • 二、开放封闭原则——OCP
    • 1、作用
    • 2、基本要点
    • 3、举例
  • 三、里氏替换原则——LSP
    • 1、作用
    • 2、基本要点
    • 3、举例
  • 四、依赖倒置原则——DLP
    • 1、作用
    • 2、基本要点
    • 3、举例
  • 五、迪米特法则——LoD
    • 1、作用
    • 2、基本要点
    • 3、举例
  • 六、接口隔离原则——ISP
    • 1、作用
    • 2、基本要点
    • 3、举例

一、单一职责原则——SRP

📌定义: 一个类应该仅有一个引起它变化的原因。

1、作用

  1. 提高可维护性
  2. 提高可扩展性
  3. 降低耦合度

2、基本要点

  1. 一个类只负责一个职责, 类的设计应该避免包含过多的功能
  2. 高内聚,低耦合:类的内部元素(方法、属性等)应该紧密相关,而与外部的类之间的依赖关系应该尽量降低
  3. 避免滥用接口: 单一职责原则并不要求每个方法都有自己的接口,而是要求类的设计在逻辑上应该是一致的,不涉及无关的功能。

3、举例

考虑一个 User 类,用于表示用户信息,例如用户名和密码。如果我们遵循单一职责原则,这个类应该只负责用户的信息表示,而不涉及与用户认证相关的逻辑。

// 不遵循单一职责原则的例子
public class User {private String username;private String password;public User(String username, String password) {this.username = username;this.password = password;}// 不应该包含与用户认证相关的逻辑public boolean authenticateUser(String enteredPassword) {return this.password.equals(enteredPassword);}
}

上述例子中,User 类不仅表示用户的信息,还包含了用户认证的逻辑。这违反了单一职责原则。更好的做法是将用户认证的逻辑移到一个独立的类中:

// 遵循单一职责原则的例子
public class User {private String username;private String password;public User(String username, String password) {this.username = username;this.password = password;}// 只负责用户信息的表示,不包含认证逻辑
}public class Authenticator {// 用户认证逻辑public boolean authenticateUser(User user, String enteredPassword) {return user.getPassword().equals(enteredPassword);}
}

二、开放封闭原则——OCP

📌定义: 类、模块、函数应该是可以扩展的,但是不可修改

一个常见的实现开放封闭原则的方式是通过使用抽象和接口。

1、作用

  1. 提高可维护性: 通过遵循开放封闭原则,系统更容易进行扩展而不需要修改已有的代码,从而降低了代码的维护成本。
  2. 提高可扩展性: 新功能的引入不应该影响已有代码,只需通过扩展来添加新功能,这提高了系统的可扩展性。
  3. 降低风险: 通过不修改已有的代码,减少了引入新功能时对系统稳定性的影响,从而降低了系统的风险。

2、基本要点

  1. 对扩展开放,对修改封闭: 意味着已有的代码不应该被修改,新功能的引入应该通过添加新的代码实现。
  2. 通过抽象实现: 通过使用抽象(接口或抽象类)来定义系统的可扩展部分,而具体的实现则通过继承或实现抽象来完成。
  3. 避免直接依赖具体实现: 在代码中尽量避免直接依赖具体的实现,而是依赖于抽象。

3、举例

例如,通过定义接口或抽象类,你可以在不修改现有代码的情况下创建新的实现,并通过接口的引用来使用不同的实现。

// 定义图形接口
public interface Shape {void draw();
}// 实现圆形
public class Circle implements Shape {@Overridepublic void draw() {System.out.println("Drawing Circle");}
}// 实现矩形
public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Drawing Rectangle");}
}// 图形绘制系统
public class DrawingSystem {// 绘制图形的方法,不依赖于具体的图形类型public void drawShape(Shape shape) {shape.draw();}
}

现在就可以实现不需要修改现有的 DrawingSystem 类,而增加新的功能。

这符合开放封闭原则,通过扩展接口和实现类来引入新的功能,而不影响已有的代码。

三、里氏替换原则——LSP

📌定义: 在任何时候,子类应该能够替代父类而不引起程序的错误。

1、作用

  1. 里氏替换原则是实现开放封闭原则的重要方式之一。
  2. 它克服了继承中重写父类造成的可复用性变差的缺点。
  3. 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

2、基本要点

  1. 子类必须保留父类的行为,即子类应该覆盖或实现父类的方法,而不应该删除、修改或使其行为不一致。
  2. 子类可以具有自己的特定行为,但不能违反父类的约定
  3. 子类重载父类方法的前置条件1可以比父类宽松,后置条件2需要比父类更加严格或者相等。

3、举例

// 符合里氏替换原则的例子// 父类
public class Shape {protected int width;protected int height;public void setWidth(int width) {this.width = width;}public void setHeight(int height) {this.height = height;}public int calculateArea() {return width * height;}
}// 子类-长方形
public class Rectangle extends Shape {// 可以保留父类的行为,也可以有自己的特定行为
}// 另一个子类-正方形
public class Square extends Shape {@Overridepublic void setWidth(int width) {super.setWidth(width);super.setHeight(width);  // 正方形的宽高一致}@Overridepublic void setHeight(int height) {super.setWidth(height);  // 正方形的宽高一致super.setHeight(height);}
}// 在使用父类的地方可以使用其任何子类
public class ExampleUsage {public void processShape(Shape shape) {int area = shape.calculateArea();// 处理其他逻辑}
}

关于里氏替换原则的例子,最有名的是“正方形不是长方形”。当然,生活中也有很多类似的例子,例如,企鹅、鸵鸟和几维鸟从生物学的角度来划分,它们属于鸟类;但从类的继承关系来看,由于它们不能继承“鸟”会飞的功能,所以它们不能定义成“鸟”的子类。同样,由于“气球鱼”不会游泳,所以不能定义成“鱼”的子类;“玩具炮”炸不了敌人,所以不能定义成“炮”的子类等。

四、依赖倒置原则——DLP

📌定义:高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象

在Java中,抽象就是抽象类/接口;细节就是实现类;高层模块就是调用端;底层模块就是具体实现类。

也就是说,模块间依赖是通过抽象发生;实现类之间没有依赖关系,所有的依赖关系通过接口/抽象类产生。

1、作用

  1. 降低类间的耦合性,提高系统的稳定性。
  2. 可以减少并行开发引起的风险。

2、基本要点

  1. 每个类尽量提供接口或抽象类,或者两者都具备。
  2. 变量的声明类型应是接口或者是抽象类。
  3. 任何类都不应该从实现类派生。
  4. 使用继承时尽量遵循里氏替换原则

3、举例

// 抽象
public interface DataService {String getData();
}// 低层模块(数据服务)实现了抽象
public class DatabaseService implements DataService {@Overridepublic String getData() {// 实际的数据获取逻辑return "Data from database";}
}// 高层模块(业务逻辑)依赖于抽象
public class BusinessLogic {private DataService dataService;// 通过构造函数注入依赖public BusinessLogic(DataService dataService) {this.dataService = dataService;}public void doSomething() {// 使用抽象而不是直接依赖于具体实现String data = dataService.getData();// 处理业务逻辑}
}

五、迪米特法则——LoD

又叫作最少知识原则(Least Knowledge Principle,LKP)

📌定义:只与你的“直接朋友”交谈,不跟“陌生人”说话。也就是说:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。

迪米特法则中的“朋友”:指的是直接的成员、局部变量、方法的参数以及当前对象创建的对象等。

迪米特原则的核心思想:

  1. 一个对象应该对其他对象有最少的了解。
  2. 类与类之间的关系应该尽量降低耦合。

1、作用

  1. 降低耦合度
  2. 提高模块的独立性: 迪米特原则鼓励将系统划分为独立的模块,每个模块只与少数几个其他模块发生直接的联系。
  3. 增强系统的可维护性: 当系统需要进行修改或扩展时,只需要关注当前对象的直接朋友,而不需要关注对象之间的详细关系。

2、基本要点

  1. 只与朋友交流: 一个对象应该对其他对象有最少的了解,只与朋友交流。
  2. 不要轻易访问非直接关联的对象的内部信息: 对于一个对象,只应该调用与之直接关联的对象的方法,而不要去调用非直接关联对象的方法。这有助于降低对象之间的耦合度。
  3. 如果需要一个对象调用另一个对象的某个方法,可以通过第三者转发这个调用

3、举例

考虑一个购物车系统,其中包含顾客、购物车和商品三个类。

根据迪米特原则,顾客类应该尽可能不直接获取商品类的信息,而是通过购物车类来处理商品的添加和删除等操作。

这样可以降低顾客类对商品类的依赖,提高系统的灵活性和可维护性。

在这里插入图片描述

// 商品类
public class Product {private String name;public Product(String name) {this.name = name;}public String getName() {return name;}
}// 购物车类
public class ShoppingCart {private List<Product> products = new ArrayList<>();public void addProduct(Product product) {products.add(product);}public void removeProduct(Product product) {products.remove(product);}
}// 顾客类
public class Customer {private ShoppingCart shoppingCart;public Customer(ShoppingCart shoppingCart) {this.shoppingCart = shoppingCart;}public void addToCart(Product product) {shoppingCart.addProduct(product);}public void removeFromCart(Product product) {shoppingCart.removeProduct(product);}
}

六、接口隔离原则——ISP

📌定义:一个类对另一个类的依赖应该建立在最小的接口上。
该原则还有另一个定义:一个类不应该被强迫依赖于它不使用的接口。

简而言之,接口隔离原则要求一个类对其他类的依赖关系应该建立在最小的接口上,而不是依赖于它不需要的接口。这样可以避免类对不必要的接口产生依赖,减少耦合,提高系统的灵活性和可维护性。

接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:

  • 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
  • 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

1、作用

  1. 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
  2. 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。

2、基本要点

  1. 一个类不应该依赖它不需要的接口。
  2. 客户端不应该被强制依赖于它不使用的方法。
  3. 接口设计应该精简明了,避免臃肿的接口。

3、举例

考虑一个接口设计的例子,有一个动物接口 Animal,其中包含了两个方法:fly()swim()

public interface Animal {void fly();void swim();}`

现在,有两个类 BirdFish 分别实现了这个接口。但是,鸟类不需要实现 swim() 方法,鱼类不需要实现 fly() 方法,这就违反了接口隔离原则。

public class Bird implements Animal {@Overridepublic void fly() {System.out.println("Bird is flying");}@Overridepublic void swim() {// Bird does not need to swim, but forced to implement the methodSystem.out.println("Bird is swimming");}}public class Fish implements Animal {@Overridepublic void fly() {// Fish does not need to fly, but forced to implement the methodSystem.out.println("Fish is flying");}@Overridepublic void swim() {System.out.println("Fish is swimming");}}

为了符合接口隔离原则,可以将 Animal 接口拆分为两个接口:IFlyableISwimmable

public interface IFlyable {void fly();}public interface ISwimmable {void swim();}public class Bird implements IFlyable {@Overridepublic void fly() {System.out.println("Bird is flying");}}public class Fish implements ISwimmable {@Overridepublic void swim() {System.out.println("Fish is swimming");}}

通过这样的设计,Bird 只需要实现 IFlyable 接口,而 Fish 只需要实现 ISwimmable 接口,避免了不必要的方法实现。这符合接口隔离原则,使得接口更加精简和灵活。


  1. 前置条件(Input Preconditions):当子类重载(overload)父类的方法时,子类的方法的输入参数要比父类的方法更宽松。这意味着子类的方法可以接受更多的输入参数类型,但不能缩小输入参数类型的范围。 ↩︎

  2. 后置条件(Output Postconditions):当子类覆写(override)或重载父类的方法时,子类方法的返回值类型和行为要比父类方法更严格或相等。这确保了子类方法的行为不会违反父类方法的契约,而是更强或相同。 ↩︎

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

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

相关文章

C++数据结构——红黑树

一&#xff0c;关于红黑树 红黑树也是一种平衡二叉搜索树&#xff0c;但在每个节点上增加一个存储位表示节点的颜色&#xff0c;颜色右两种&#xff0c;红与黑&#xff0c;因此也称为红黑树。 通过对任意一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树可以…

(2)SpringBoot学习——芋道源码

Spring Boot 的自动配置 1.概述 EmbeddedWebServerFactoryCustomizerAutoConfiguration 类 Configuration // <1.1> ConditionalOnWebApplication // <2.1> EnableConfigurationProperties(ServerProperties.class) // <3.1> public class EmbeddedWebSe…

推荐一个好用的旧版本软件安装包下载地址

最近要下载旧版本的mysql和postman&#xff0c;发现官网和其他博客里边提供的地址&#xff0c;要不都非常难找到相应的下载链接&#xff0c;要不就是提供的从别的地方复制过来的垃圾教程&#xff0c;甚至有的下载还要积分&#xff0c;反正是最后都没下载成功&#xff0c;偶然发…

nodejs基于vue奖学金助学金申请系统08ktb

高校奖助学金系统的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&#xff0c;…

期末成绩群发给家长

每当学期结束&#xff0c;老师们的邮箱和手机便会被成绩报告单填满。那么&#xff0c;如何高效地将成绩群发给家长呢&#xff1f; 一、邮件还是短信&#xff1f; 首先&#xff0c;选择一个合适的通讯方式是关键。邮件正式且便于附件&#xff0c;但短信更快捷。考虑到大多数家长…

windows平台使用tensorRT部署yolov5详细介绍,整个流程思路以及细节。

目录 Windows平台上使用tensorRT部署yolov5 前言&#xff1a; 环境&#xff1a; 1.为什么要部署&#xff1f; 2.那为什么部署可以解决这个问题&#xff1f;&#xff08;基于tensorRT&#xff09; 3.怎么部署&#xff08;只讨论tensorRT&#xff09; 3.0部署的流程 3.1怎…

UE4学习笔记 FPS游戏制作2 制作第一人称控制器

文章目录 章节目标前置概念Rotator与Vector&#xff1a;roll与yaw与pitch 添加按键输入蓝图结构区域1区域2区域3区域4 章节目标 本章节将实现FPS基础移动 前置概念 Rotator与Vector&#xff1a; Vector是用向量表示方向&#xff0c;UE中玩家的正前方是本地坐标系的(1,0,0)&…

【Linux】信号量

信号量 一、POSIX信号量1、信号量的原理2、信号量的概念&#xff08;1&#xff09;PV操作必须是原子操作&#xff08;2&#xff09;申请信号量失败被挂起等待 3、信号量函数4、销毁信号量5、等待信号量&#xff08;申请信号量&#xff09;6、发布信号量&#xff08;释放信号量&…

20240131在WIN10下配置whisper

20240131在WIN10下配置whisper 2024/1/31 18:25 首先你要有一张NVIDIA的显卡&#xff0c;比如我用的PDD拼多多的二手GTX1080显卡。【并且极其可能是矿卡&#xff01;】800&#xffe5; 2、请正确安装好NVIDIA最新的545版本的驱动程序和CUDA。 2、安装Torch 3、配置whisper http…

仰暮计划|“从米票、肉票、糖果票到肥皂票、煤票、棉花票等,生活里头的方方面面都能用粮票买到”

口述人&#xff1a;牛翠英(女) 整理人&#xff1a;霍芝冉 口述人基本信息&#xff1a;现68岁&#xff0c;河南省安阳市北关区霍家村人&#xff0c;现居河南安阳市区。 奶奶一生辛劳&#xff0c;操持家务&#xff1b;亲眼见证了时代变迁&#xff0c;社会发展&#xff0c;…

docker笔记整理

Docker 安装 添加yum源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 安装docker yum -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin 启动docker systemctl start docker 查看docker状态 s…

09. 异常处理

目录 1、前言 2、常见的异常 3、异常处理try...except...finally 4、异常信息解读 5、raise 6、自定义异常 7、小结 1、前言 在编程中&#xff0c;异常&#xff08;Exception&#xff09;是程序在运行期间检测到的错误或异常状况。当程序执行过程中发生了一些无法继续执…

C languange DGEQRF 示例,link liblapack.a

1.示例源码 #include <stdio.h>int min(int m, int n){ return m<n? m:n;}void print_matrix(double* A, int m, int n, int lda) {for (int i 0; i < m; i){for (int j 0; j < n; j){//printf("%7.4f ", A[i j*lda]);printf("%7.4f, &quo…

创建表与删除表(六)

表的基本操作&#xff08;六&#xff09; 一、创建表 1.1 使用DDL语句创建表 CREATE TABLE 表名(列名 类型,列名 类型......); 示例&#xff1a; 创建一个 employees 表包含雇员 ID &#xff0c;雇员名字&#xff0c;雇员薪水。 create table employees(employee_id int,em…

从创新者到引领者:探索第四范式的AI之旅

大数据产业创新服务媒体 ——聚焦数据 改变商业 如今&#xff0c;人工智能已成为改变世界、驱动各行各业变革的核心源动力。在国内&#xff0c;有一些公司已走在前列&#xff0c;其中就包括北京第四范式智能技术股份有限公司&#xff0c;在AI这个赛道&#xff0c;他是一名创新…

Patch2QL:开源供应链漏洞挖掘和检测的新方向

背景 开源生态的上下游中&#xff0c;漏洞可能存在多种成因有渊源的其它缺陷&#xff0c;统称为“同源漏洞”&#xff0c;典型如&#xff1a; 上游代码复用缺陷。开源贡献者在实现功能相似的模块时&#xff0c;常复用已有模块代码或逻辑&#xff1b;当其中某个模块发现漏洞后…

java:java反编译工具--jd-gui

JD-GUI是一款反编译软件&#xff0c;JD分为JD-GUI、JD-Eclipse两种运行方式&#xff0c;JD-GUI是以单独的程序的方式运行&#xff0c;JD-Eclipse则是以一个Eclipse插件的方式运行。 官方下载地址&#xff1a; https://github.com/java-decompiler/jd-gui/releases 我这边下载…

Unity Shader 滚动进度条效果

Unity Shader 滚动进度条效果 前言项目场景布置导入图片修改场景设置修改图片尺寸即可调整进度 ASE连线 前言 UI要实现一个滚动进度&#xff0c;于是使用Shader制作一个。 项目 场景布置 导入图片 修改一下导入图片的格式&#xff0c;这样才能循环起来 WrapMode改为Repea…

正点原子--STM32中断系统学习笔记(1)

1、什么是中断&#xff1f; 原子哥给出的概念是这样的&#xff1a;打断CPU正常执行的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断。 当发生中断时&#xff0c;当前执行的程序会被暂时中止&#xff0c;进而进入中断处理函…

Node: opensslErrorStack: [ ‘error:03000086:digital envelope routines::initialization error‘ ]异常处理

目录 一、问题描述二、问题分析三、解决方案方案一&#xff1a;你可以按照以下步骤来删除 NODE_OPTIONS 环境变量中的 --openssl-legacy-provider 选项&#xff1a;方案二&#xff1a;在package.json更改scripts方案三&#xff1a;降级 Node.js 版本 在进行前端项目开发时&…