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

news2024/11/28 8:35:45

设计模式六大原则

目录

  • 一、单一职责原则——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 {
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

// 实现矩形
public class Rectangle implements Shape {
    @Override
    public 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 {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);  // 正方形的宽高一致
    }

    @Override
    public 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 {
    @Override
    public 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 {

  @Override

  public void fly() {

    System.out.println("Bird is flying");

  }

  @Override

  public void swim() {

    // Bird does not need to swim, but forced to implement the method

    System.out.println("Bird is swimming");

  }

}

public class Fish implements Animal {

  @Override

  public void fly() {

    // Fish does not need to fly, but forced to implement the method

    System.out.println("Fish is flying");

  }

  @Override

  public 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 {

  @Override

  public void fly() {

    System.out.println("Bird is flying");

  }

}

public class Fish implements ISwimmable {

  @Override

  public void swim() {

    System.out.println("Fish is swimming");

  }

}

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


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

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

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

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

相关文章

Arcgis10.3安装

所需软件地址 链接&#xff1a;https://pan.baidu.com/s/1aAykUDjkaXjdwFjDvAR83Q?pwdbs2i 提取码&#xff1a;bs2i 1、安装License Manager 点击License Manager.exe&#xff0c;默认下一步。 安装完&#xff0c;点击License Server Administrator&#xff0c;停止服务。…

Java基础 集合(二)List详解

目录 简介 数组与集合的区别如下&#xff1a; 介绍 AbstractList 和 AbstractSequentialList Vector 替代方案 Stack ArrayList LinkedList 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界…

IT行业中最重要的证书

在IT行业&#xff0c;拥有一些含金量较高的证书是职业发展的关键。这些证书不仅可以证明技能水平&#xff0c;还有助于提升在职场上的竞争力。本文将介绍几个IT行业中最重要的证书。 1. Cisco认证 CCNA&#xff08;Cisco Certified Network Associate&#xff09;是Cisco公司新…

ArcGIS学习(二)属性表的基本操作

ArcGIS学习(二)属性表的基本操作 1.查看属性表 ArcGIS是处理空间数据的平台。对于空间数据,大家可以理解成它是由两个部分构成:1.一个是空间形体,也就是点、线、面三种。线又可以分为直线、曲线,面又分为圆形、正方形、不规则形体等;2.另外一个部分是空间形体所附带的…

【深蓝学院】移动机器人运动规划--第3章 基于采样的路径规划--笔记

0. Preliminaries 做规划都是将WS转到C space下进行。 找到可行解和最优解&#xff08;这两个不同&#xff09; 通过增量或者批次地在C-space中采样来增量式地构建树或者图。 不显式地构造 如果把整个规划问题看成一个大的优化问题&#xff0c;那么大问题可以拆分成小问题进行…

09. 异常处理

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

Monday.com替代工具大盘点:哪个更适合您的团队协作需求?

市场上有许多项目管理软件解决方案&#xff0c;每个都有自己的优点和缺点&#xff0c;根据您的具体需求和要求&#xff0c;市场上有8种可用的项目管理软件可以作为Monday.com的替代工具&#xff0c;分别是&#xff1a;Zoho Projects、Trello、Asana、Wrike、Basecamp、JIRA、Mi…

Hadoop-生产调优(更新中)

第1章 HDFS-核心参数 1.1 NameNode内存生产配置 1&#xff09;NameNode 内存计算 每个文件块大概占用 150 byte&#xff0c;一台服务器 128G 内存为例&#xff0c;能存储多少文件块呢&#xff1f; 128 * 1024 * 1024 * 1024 / 150byte ≈ 9.1 亿G MB KB Byte 2&#xff09…

【Redis】签到点赞和UV统计

Redis签到点赞和UV统计 点赞 点赞功能分析 需求&#xff1a; 同一个用户只能点赞一次&#xff0c;再次点击则取消点赞如果当前用户已经点赞&#xff0c;则点赞按钮高亮显示&#xff08;前端判断字段isLike属性&#xff09; 实现步骤&#xff1a; 利用Redis的set集合判断是…

安科瑞光伏、储能一体化监控及运维解决方案

上海安科瑞电气股份有限公司 胡冠楠 咨询家&#xff1a;“Acrelhgn”&#xff0c;了解更多产品资讯 前言 今年以来&#xff0c;在政策利好推动下光伏、风力发电、电化学储能及抽水蓄能等新能源行业发展迅速&#xff0c;装机容量均大幅度增长&#xff0c;新能源发电已经成为新…

Nicn的刷题日常之带空格直角三角形图案

1.题目描述 描述 KiKi学习了循环&#xff0c;BoBo老师给他出了一系列打印图案的练习&#xff0c;该任务是打印用“*”组成的带空格直角三角形图案。 输入描述&#xff1a; 多组输入&#xff0c;一个整数&#xff08;2~20&#xff09;&#xff0c;表示直角三角形直角边的长度&am…

STM32F407移植OpenHarmony笔记7

继上一篇笔记&#xff0c;成功启动了liteos_m内核&#xff0c;可以创建线程了&#xff0c;也能看到shell控制台了。 今天研究文件系统&#xff0c;让控制台相关文件命令如mkdir和ls能工作。 liteos_m内核支持fatfs和littlefs两个文件系统&#xff0c; fatfs适用于SD卡&#xff…

【Tomcat与网络6】 Tomcat是如何扩展Java线程池的?

目录 1.Java 的线程池 2.Tomcat 的线程池 学习Tomcat的时候&#xff0c;有很多绚丽的技术值得我们学习&#xff0c;但是个人认为Tomcat的线程池扩展是最值得研究的一个部分&#xff0c;线程池的应用太广了&#xff0c;也重要了&#xff0c;Java原生线程池的特征我相信很多人都…

【Redis】一文搞懂redis的所有知识点

目录 1. 什么是Redis&#xff1f;它主要用来什么的&#xff1f; 2.说说Redis的基本数据结构类型 2.1 Redis 的五种基本数据类型​编辑 2.2 Redis 的三种特殊数据类型 3. Redis为什么这么快&#xff1f;​编辑 3.1 基于内存存储实现 3.2 高效的数据结构 3.3 合理的数据编…

Python 中常用图像数据结构

&#xff08;原文&#xff1a;https://blog.iyatt.com/?p13222 &#xff09; 1 测试环境 Python 3.12.1 numpy 1.26.3 opencv-python 4.9.0.80 pillow 10.2.0 matplotlib 3.8.2 注&#xff1a; 基于 2022.1.16 和 2022.4.9 的三篇博文再次验证并重写&#xff0c;原文已删…

嵌入式学习第十五天

内存管理: 1.malloc void *malloc(size_t size); 功能: 申请堆区空间 参数: size:申请堆区空间的大小 返回值: 返回获得的空间的首地址 失败返回NULL 2.free void free(void *ptr); 功能: 释放堆区空间 注…

复刻桌面小电视【包含代码分析】

宗旨&#xff1a;开源、分享、学习、进步&#xff0c;生命不息&#xff0c;折腾不止。 复刻小电视 感谢各位大佬的开源项目&#xff0c;让我有了学习的机会&#xff0c;如果侵权&#xff0c;请联系我删除。本人能力有限&#xff0c;如果有什么不对的地方&#xff0c;欢迎指正…

Vue3-Composition-API(二)

一、computed函数使用 1.computed 在前面我们讲解过计算属性computed&#xff1a;当我们的某些属性是依赖其他状态时&#xff0c;我们可以使用计算属性来处理 在前面的Options API中&#xff0c;我们是使用computed选项来完成的&#xff1b; 在Composition API中&#xff0c…

《C程序设计》上机实验报告(四)之一维数组

1.运行程序 #include <stdio.h> void main( ) { int a[5],i,j; for(i1;i<5;i) a[i]0; for(i1;i<5;i) for(j1;j<5;j) a[j]a[i]1; printf("%d %d\n",a[0],a[3]); } 要求&#xff1a; &#xff08;1&#xff09;输入并调试上述源程序&#xff0c;…

安装 vant-ui 实现底部导航栏 Tabbar

本例子使用vue3 介绍 vant-ui 地址&#xff1a;介绍 - Vant 4 (vant-ui.github.io) Vant 是一个轻量、可定制的移动端组件库 安装 通过 npm 安装&#xff1a; # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant # Vue 2 项目&#xff0c;安装 Vant 2 npm i vantlatest-v…