设计模式01-七大设计原则

文章目录

  • 设计模式01-七大设计原则
    • 开闭原则-Open Close
    • 依赖倒置原则-Dependence Inversion
    • 单一职责原则-Simple ResponsiBility
    • 接口隔离原则-Interface Segregation
    • 迪米特法则-Law of Demeter
    • 里氏替换原则-Liskov Substitution
    • 合成(组合)、复用原则-Composite&Aggregate Reuse

开闭原则-Open Close

一个软件实体如类、模块、函数应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。提高程序的复用性和可维护性

案例:一个课程类,具有:课程类型、课程名称、售价3个属性,代码实现:

课程接口:

public interface ICourse {// 获取类型String getCategory();// 获取名称String getName();// 获取价格Double getPrice();
}

Java课程实现类:

public class JavaCourse implements ICourse {private String name;private Double price;private String category;public JavaCourse(String name, Double price, String category) {this.name = name;this.price = price;this.category = category;}@Overridepublic String getCategory() {return category;}@Overridepublic String getName() {return name;}@Overridepublic Double getPrice() {return price;}
}

测试类:

public static void main(String[] args) {JavaCourse course = new JavaCourse("架构师", 998.00, "Java");System.out.println("课程:" + course.getName() + " 分类:" + course.getCategory() + " 价格:" + course.getPrice());
}

问题:此时要根据情况修改价格,如双11打8折,春节打五折,那我们直接在getPrice()方法中修改显然违背了开闭原则中对修改关闭的定义,比如下面这样:

@Override
public Double getPrice() {return price * 0.8;// 双11打8折
}

这样会带来的问题

  1. 频繁修改已有代码逻辑,如春节、双11、双12各有不同的折扣力度,每种会员可能又有不同的折扣计算方式,这时候都要来修改我们已有的程序,降低了程序的可维护性
  2. 违背了开闭原则

这时候该怎么办呢?当然是通过对扩展开放的定义来扩展我们的程序:

比如我们可以定义一个JavaCourse的折扣类来实现这个逻辑:

public class JavaDiscountCourse extends JavaCourse {public JavaDiscountCourse(String name, Double price, String category) {super(name, price, category);}public Double getDiscountPrice() { // 获取折扣价格return super.getPrice() * 0.8;}
}

然后修改我们的主程序来进行测试:

public static void main(String[] args) {JavaCourse course = new JavaDiscountCourse("架构师", 998.00, "Java");System.out.println("课程:" + course.getName() +" 分类:" + course.getCategory() +" 折扣价格:" + course.getPrice() +"原价:" + ((JavaDiscountCourse) course).getDiscountPrice());
}

这样即符合了开闭原则的理念,又提高了我们程序的复用性和可维护性。

依赖倒置原则-Dependence Inversion

定义高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;针对接口编程,不针对实现编程

优点可以减少类之间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险

高层低层的概念: 离调用者越近,层次越高。离被调用者越近,层次越低。如我去调用别人的接口的某个方法,那我的代码就是高层模块,被调用的就是底层。

高层依赖低层模块可能造成的问题:低层方法参数修饰符、返回值类型等发生了变化,那么高层调用者也要做出相应的变化。

案例:Tom类需要学习Java、Python两门课程

Tom类(被调用方:低层):

public class Tom {public void studyJava() {System.out.println("学习Java课程");}public void studyPython() {System.out.println("学习Python课程");}}

测试类(也就是我们的高层调用):

public class DIDemo {public static void main(String[] args) {Tom tom = new Tom();tom.studyJava();tom.studyPython();}
}

这时,程序看起来没什么问题。但是如果将Tom中的studyJava()方法改成javaStudy() 那么高层的调用就也需要修改了。这样的话就提高了修改造成的风险,而且程序的耦合性强。

接下来做一下修改:高层和低层都依赖抽象

首先建立抽象(课程的抽象接口):

public interface ICourse {void study();
}

然后创建Java和Python的学习类,并实现课程接口:

public class JavaStudy implements ICourse {@Overridepublic void study() {System.out.println("学习Java");}
}public class PythonStudy implements ICourse {@Overridepublic void study() {System.out.println("学习Python");}
}

这时Tom类(低层)依赖抽象:

public class Tom {// 依赖了抽象ICoursepublic void study(ICourse iCourse) {iCourse.study();}
}

这时我们的测试类(高层)就可以修改为依赖抽象而不是细节:

public static void main(String[] args) {Tom tom = new Tom();tom.study(new JavaStudy());tom.study(new PythonStudy());
}

这样就降低了我们程序的耦合度,提高了程序稳定性和可读性降低了修改程序带来的风险

版本3:当然我们也可以通过构造方法来优化Tom类:

public class Tom {private ICourse iCourse;public Tom(ICourse iCourse) {this.iCourse = iCourse;}public void study() {iCourse.study();}}

这样的话只需要在测试类中传入ICourse的实现类来构造Tom即可,同样也可以使用Set等方式进行注入。

一句话总结:面向接口编程

单一职责原则-Simple ResponsiBility

定义:不要存在多于一个导致类变更的原因。说白了:一个类、接口、方法只负责一项职责

优点:降低代码复杂度,提高程序可读性,提高系统可维护性,降低变更引起的风险

案例:观看两种课程,直播课不能快进,录播课可以快进

课程类(针对不同课程有不同处理逻辑):

public class Course {public void study(String courseName) {if ("直播课".equals(courseName)) {System.out.println("直播课不能快进");} else {System.out.println("录播课可以快进");}}
}

测试类:

public class StudyDemo {public static void main(String[] args) {Course course = new Course();course.study("直播课");course.study("录播课");}
}

这时候如果我们要对不同类型课程做不同的处理,比如编码解码处理,可能就需要针对Course#study方法进行修改,势必会增加代码复杂度,降低可读性。

接下来我们这样修改:分别在不同的类里面处理不同的课程:

直播课类

public class LiveCourse {public void study(String courseName) {System.out.println(courseName + "只能在线观看");}
}

录播课类

public class ReplayCourse {public void study(String courseName) {System.out.println(courseName + "可以反复观看");}
}

这样我们就可以直接调用不同的类进行处理。降低了复杂度,提高了可读性。

但是后面可能针对课程有很多新的职责:比如获取视频流、退款、学习课程、获取课程基本信息,这时候该怎么做呢?

接口级别

如用户接口,可以看视频和退款:

public interface ICourseManager {void readVideo();// 看视频void refundCourse();// 退款
}

课程信息接口,可以用来获取课程信息:

public interface ICourseInfo {String getCourseName();// 获取课程信息
}

这样的话,新增课程信息不会对其他职责造成影响,就满足了单一职责的定义

public class CourseImpl implements ICourseInfo, ICourseManager {@Overridepublic String getCourseName() {return null;}@Overridepublic void readVideo() {}@Overridepublic void refundCourse() {}
}

方法级别

以修改用户信息为例

public class LoginMethod {private void updateUserName() {// 修改用户名}private void updateUserPhone() {// 修改用户手机号}private void updateUserAddress() {// 修改用户地址}
}

如上,较小颗粒度的拆分方法职责,使其看起来明了,并且职责清晰,不会相互影响。

接口隔离原则-Interface Segregation

定义:用多个专门的接口,而不是使用单一的总接口,客户端不应该依赖它不需要的接口

注意:一个类对应一个类的依赖应该建立在最小的接口上;建立单一接口,不要建立庞大臃肿的接口;尽量细化接口,接口中的方法尽量少;注意适度原则,一定要适度

优点:高内聚,低耦合,从而提高可读性,可扩展性和可维护性

比如我们有一个动物接口,内部提供了一些方法:

public interface IAnimal {// 跑void run();// 飞void fly();// 游泳void swim();// 吃东西void eat();}

这时候假如我们有一个鸟类,实现该接口的话就需要实现一些不需要实现的方法,如游泳。

这时候我们可以针对:吃、跑、飞、游泳分别建立接口并提供方法,这时候鸟类只需要实现吃和飞的接口就可以了。

迪米特法则-Law of Demeter

定义:一个对象应该对其他对象保持最少的了解,又叫最少知道原则

强调只和朋友交流,不和陌生人说话

朋友的概念:出现在成员变量、方法输入、输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类

作用:一定程度上解耦

案例:团队领导让员工查询现有的课程数量

课程类:

public class Course {private String name;public String getName() {return name;}public Course setName(String name) {this.name = name;return this;}
}

员工类(用来查询课程数量):

public class Employee {public int getCourseNumber(List<Course> courses) {return courses.size();}
}

团队领导类:

public class TeamLeader {public int getNumbersByEmployee(Employee employee) {List<Course> courses = new ArrayList<>();for (int i = 0; i < 20; i++) {courses.add(new Course());}return employee.getCourseNumber(courses);}
}

接下来是测试方法:

public static void main(String[] args) {Employee employee = new Employee();TeamLeader teamLeader = new TeamLeader();System.out.println(teamLeader.getNumbersByEmployee(employee));
}

这里就发现问题了:TeamLeader引用的Course类并不满足迪米特法则,即Course在TeamLeader中并不是其“朋友”。

里氏替换原则-Liskov Substitution

定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2就是类型T1的子类型

定义扩展:一个软件实体如果适用一个父类的话,那一定适用其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能替换父类对象,而程序逻辑不变

引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能,比如前面开闭原则的案例,打折课程类新写了一个打折方法:

public class JavaDiscountCourse extends JavaCourse {public JavaDiscountCourse(String name, Double price, String category) {super(name, price, category);}public Double getDiscountPrice() { // 获取折扣价格return super.getPrice() * 0.8;}}

含义1:子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法

含义2:子类中可以增加自己特有的方法

含义3:子类方法重载父类的方法时,方法的前置条件(即方法的输入、入参)要比父类的输入参数更宽松

含义4:当子类的方法实现父类的方法时(重写、重载或实现抽象方法),方法的后置条件(即方法的输出、返回值)要比父类更严格或相等

优点1:约束继承泛滥,开闭原则的一种体现

优点2:加强程序的健壮性,同时变更时也可以做到非常好的兼容性提高程序的维护性,扩展性。降低需求变更时引入的风险

例子:以正方形长方形为例,在测试类中获取长方形的宽高,如果宽大于高就修改参数,直至宽小于等于高,代码如下:

长方形类:

public class Rectangle {private Integer width;// 宽private Integer height;// 高public Integer getWidth() {return width;}public Rectangle setWidth(Integer width) {this.width = width;return this;}public Integer getHeight() {return height;}public Rectangle setHeight(Integer height) {this.height = height;return this;}
}

正方形类:

public class Square extends Rectangle {private Integer length;public Integer getLength() {return length;}public Square setLength(Integer length) {this.length = length;return this;}@Overridepublic Integer getWidth() {return this.length;}@Overridepublic Rectangle setWidth(Integer width) {return setLength(width);}@Overridepublic Integer getHeight() {return this.length;}@Overridepublic Rectangle setHeight(Integer height) {return setLength(height);}
}

可以看到为了满足正方形的边长相等的属性,我们修改了其父类的width height的get和set方法。

接下来是测试类,定义一个resize()方法,如果长方形的宽大于高,就在while中将高度+1:

public class TestDemo {private static void resize(Rectangle rectangle) {while (rectangle.getWidth() >= rectangle.getHeight()) {rectangle.setHeight(rectangle.getHeight() + 1);System.out.println("当前宽度:" + rectangle.getHeight());}}public static void main(String[] args) {Rectangle rectangle = new Rectangle();rectangle.setWidth(20);rectangle.setHeight(10);resize(rectangle);}
}

这里我们用父类进行测试,控制台打印如下:

当前宽度:11
当前宽度:12
当前宽度:13
当前宽度:14
当前宽度:15
当前宽度:16
当前宽度:17
当前宽度:18
当前宽度:19
当前宽度:20
当前宽度:21Process finished with exit code 0

接下来用子类Square进行测试:

当前宽度:407744
当前宽度:407745
当前宽度:407746
当前宽度:407747
当前宽度:407748
当前宽度:407749
...无限循环

可以看到这个resize()由于传的是子类,从而破坏了该方法的正常逻辑,也不满足里氏替换原则。

再回顾一下定义:使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化

那么上面的问题如何解决呢?

定义一个四边形接口:

public interface Quadrangle {Integer getHeight();// 获取高度Integer getWidth();// 获取宽度
}

定义正方形类:

public class Square implements Quadrangle {private Integer length;public Square setLength(Integer length) {this.length = length;return this;}@Overridepublic Integer getHeight() {return this.length;}@Overridepublic Integer getWidth() {return this.length;}
}

定义长方形类:

public class Rectangle implements Quadrangle {private Integer width;// 宽private Integer height;// 高public Rectangle setWidth(Integer width) {this.width = width;return this;}public Rectangle setHeight(Integer height) {this.height = height;return this;}@Overridepublic Integer getHeight() {return this.height;}@Overridepublic Integer getWidth() {return this.width;}
}

测试程序:

public class TestDemo {private static void resize(Quadrangle quadrangle) {while (quadrangle.getWidth() >= quadrangle.getHeight()) {// 由于四边形没有提供set方法所以这里会报错quadrangle.setHeight(quadrangle.getHeight() + 1);System.out.println("当前宽度:" + quadrangle.getHeight());}}public static void main(String[] args) {Square square = new Square();square.setLength(10);// 边长为10的正方形resize(square);}
}

这里由于四边形类并没提供setHeight()方法,所以这里的第五行代码会报错,从一定程度上避免了继承泛滥。

合成(组合)、复用原则-Composite&Aggregate Reuse

定义:尽量使用对象组合、聚合,而不是继承关系达到软件复用的目的

聚合:has - a , 比如电脑和U盘,可以在一起工作,电脑也可以单独工作

组合:contains - a,比如人体的各个部位,组合在一起才能有完整的生命周期

继承:is - a

优点:可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少

rangle.getWidth() >= quadrangle.getHeight()) {
// 由于四边形没有提供set方法所以这里会报错
quadrangle.setHeight(quadrangle.getHeight() + 1);
System.out.println(“当前宽度:” + quadrangle.getHeight());
}
}

public static void main(String[] args) {Square square = new Square();square.setLength(10);// 边长为10的正方形resize(square);
}

}


这里由于四边形类并没提供setHeight()方法,所以这里的第五行代码会报错,从一定程度上避免了继承泛滥。## 合成(组合)、复用原则-Composite&Aggregate Reuse定义:**尽量使用对象组合、聚合,而不是继承关系达到软件复用的目的**聚合:**has - a** , 比如电脑和U盘,可以在一起工作,电脑也可以单独工作组合:**contains - a**,比如人体的各个部位,组合在一起才能有完整的生命周期继承:**is - a**优点:**可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少****一句话总结:能不用继承就不用继承**最后总结:设计模式是对我们开发中做的一些规范和约束,在实际的开发中并非要追求完美,而是在时间、成本等各方面允许的情况下尽量遵守规范。

设计模式01-七大设计原则相关推荐

  1. 【设计模式】设计模式总结 ( 七大设计原则 | 创建型模式 | 结构型模式 | 行为型模式 ) ★★★

    文章目录 一.七大设计原则 1.开闭原则 2.依赖倒置原则 3.单一职责原则 4.接口隔离原则 5.迪米特原则 6.里氏替换原则 7.合成复用原则 二.创建型模式 0.简单工厂模式 ( 不属于 GOF ...

  2. 接口隔离原则_设计模式之七大设计原则(上)

    这是雷总20多年前写的汇编程序,像诗一样优雅 而中国诗词讲究平仄.押韵.对仗等一系列的规则原则, 那么想要写出像诗一样优雅的程序你不得不知的 -- 设计模式之七大原则: 1.开闭原则:对修改关闭,对扩 ...

  3. Java设计模式--------面向对象七大设计原则

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.单一职责原则 二.开闭原则 三.接口隔离原则 什么是接口隔离原则 接口隔离原则分析 四.里氏替换原则 继承 为什么 ...

  4. 设计模式的七大设计原则:其七:合成复用原则

    合成复用原则(Composite Reuse Principle) 基本介绍: 原则是尽量使用合成/聚合的方式,而不是使用继承. 设计原则核心思想: 1.找出应用中可能需要变化之处,把它们独立出来,不 ...

  5. 设计模式的七大设计原则:其五:开闭原则

    开闭原则: 基本介绍: 1.开闭原则(Open Closed Principle)是编程中最基础,最重要的原则. 2.一个软件实体如类,模块和函数应该对扩展开饭,对修改关闭.用抽象构建框架,用实现扩展 ...

  6. 设计模式的七大设计原则:其三:依赖倒转原则

    依赖倒转原则: 基本介绍: 依赖倒转原则(Dependence Inversion Principle)是指: 1.高层模块不应该依赖低层模块,二者都应该依赖其抽象 2.抽象不应该依赖细节,细节应该依 ...

  7. 设计模式的七大设计原则:其六:迪米特法则

    迪米特法则: 基本介绍: 1.一个对象应该对其他对象保持最少的了解 2.类与类关系越密切,耦合度越大 3.迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的 ...

  8. 设计模式的七大设计原则:其四:里氏替换原则

    里氏替换原则: OO中的继承性的思考和说明: 1)继承包含这样一层含义:父类中凡是已经实现好的方法,实际上时在是设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的 ...

  9. 设计模式的七大设计原则:其二:接口隔离原则

    接口隔离原则: 基本介绍: 1.客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上 2.先看一张图 3.类A通过Interface1依赖类B,类C通过接口Interface ...

  10. 设计模式的七大设计原则:其一:单一职责原则

    单一职责原则: 单一职责原则注意事项和细节: 1.降低类的复杂度,一个类只负责一项职责 2.提高类的可读性,可维护性 3.降低变更引起的风险 4.通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单 ...

最新文章

  1. 开发中新游戏《庞加莱》
  2. jQuery EasyUI API 中文文档 - DataGrid 数据表格
  3. 3D目标检测多模态融合算法综述
  4. MySQL数据库(八) 一一 数据库信息和使用序列
  5. 配置maven mvn命令使用jdk 1.7编译
  6. qpython3使用手册图_qpython图形
  7. Shell入门:掌握Linux,OS X,Unix的Shell环境
  8. parallels desktop
  9. CSS定位中“父相子绝”
  10. C++ 模板和 C# 泛型之间的区别(C# 编程指南)
  11. Hibernate之组件映射
  12. 修改FTP服务器时长,连接ftp服务器的时长怎么设置
  13. Gravatar - globally recognized avatar
  14. android 版本更新下载进度框,Android版本更新(Service下载 Notification进度条)
  15. Linux常用工具包安装
  16. 快逸报表数据库密码加密解决方案
  17. UVALive 7269 Snake Carpet
  18. Spring Boot整合Redis
  19. transform 实现 附加鼠标悬浮效果,照片旋转,六面体,3D效果
  20. 超级详细配置SSM (Intellij idea + Maven + Spring + SpringMVC + MyBatis + c3p0 )

热门文章

  1. android 系统自带的软件可以删除列表--Defy
  2. Ribbon与Hystrix
  3. 阿里开源新一代单元测试 Mock 工具!
  4. python 线性回归显著性检验_回归方程及回归系数的显著性检验_stata显著性检验...
  5. 《2022中国供应链物流创新科技报告》:菜鸟、顺丰、JDL、极智嘉、旷视、富勒、易流等超百家企业科技产品方案全公开!(附下载)...
  6. hive之full outer join(全连接)使用方法
  7. R 软件的下载与安装
  8. /Users/xxxx/.zshrc:export:101: not valid in this context: /Users/xxxx/xxxx
  9. linux 进程hang,GoldenGate Extract进程hang问题解决一例
  10. HttpClient 爬取百度图片