场景

1、开闭原则(Open-Closed Principle,OCP)

是指一个软件实体(如类、模块和函数)应该对外扩展开放,对修改关闭。所谓的关闭,也正是对扩展和修改两个行为的一个原则。

它强调的是用抽象构建框架,用实现扩展细节,可以提高软件系统的可复用性和可维护性。

开闭原则是面向对象设计的最基本原则,例如版本更新,可以实现尽量不修改源代码的前提下增加新功能。

2、依赖倒置原则(Dependence Inversion Principle, DIP)

是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象。

抽象不应该依赖细节,细节应该依赖抽象。可以减少类与类之间的耦合性,提高系统的稳定性,提高代码可读性和可维护性,

降低修改程序的风险。

3、单一职责原则(Simple Responsibility Pinciple,SRP)

是指不要存在多于一个导致变更的原因。如果一个类负责两个职责,

修改其中一个,则可能导致另一个出现问题。所以将多个职责用多个类进行解耦。

4、接口隔离原则(Interface Segregation Principle,ISP)

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

①一个类对另一个类的依赖应该建立在最小的接口之上。

②建立单一接口,不要建立庞大臃肿的接口。

③尽量细化接口,接口中的方法尽量少(适度,不是越少越好)

接口隔离原则符合高内聚低耦合的设计思想,可以使类具有很好的可读性、可扩展性。

5、迪米特原则(Law of Demeter LoD)

是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合度。

迪米特原则主要强调:只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称为成员朋友类,

而出现在方法体内部的类不属于朋友类,也就是说陌生的类不要以局部变量的形式出现在类的内部。

6、里氏替换原则(Liskov Substitution Principle,LSP)

是指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换为O2时,程序P

的行为没有发生变化,那么类型T2是类型T1的子类型。

可以理解为一个软件实体如果适用于一个父类,那么一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,

子类对象能够替换父类对象,而程序逻辑不变。

或者说子类可以扩展父类的功能,但不能改变父类原有的功能:

①子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

②子类可以增加自己特有的方法。

③当子类的方法重载父类的方法时,方法的前置条件(方法的输入/入参)要比父类方法的输入参数更宽松。

④当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(方法的输出/返回值)要比父类更严格或与父类一样。

7、合成复用原则(Composite/Aggregate Reuse Principle,CARP)

是指尽量使用对象组合/聚合而不是继承关系达到软件复用的目的。可以使系统更加灵活,降低类与类之间的耦合度,

一个类的变化对其他类造成的影响相对较少。

继承叫做白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合称为黑箱复用,我们是无法获取到类以外的对象的实现细节的。

虽然我们要根据具体的业务场景来做代码设计,但也需要遵循OOP模型。

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

开闭原则示例

1、以商城商品为例,新建一个商品接口Goods

package com.ruoyi.demo.designPrinc.ocp;/*** 商品接口*/
public interface IGoods {Integer getId();String getName();Double getPrice();
}

2、商品有很多种类,这里新建一个电脑的类Computer

package com.ruoyi.demo.designPrinc.ocp;public class Computer implements IGoods{private Integer Id;private String name;private Double price;public Computer(Integer id, String name, Double price) {Id = id;this.name = name;this.price = price;}@Overridepublic Integer getId() {return this.Id;}@Overridepublic String getName() {return this.name;}@Overridepublic Double getPrice() {return this.price;}
}

3、现在要给电脑做活动,价格打88折。

如果修改Computer中的getPrice()方法,则存在一定风险,可能会影响另外调用获取价格的地方。既要不修改原有代码,又

要实现价格优惠这个功能,可以写一个处理优惠逻辑的类ComputerDiscounts

package com.ruoyi.demo.designPrinc.ocp;/*** 要给电脑类做活动,搞优惠。如果修改Computer的getPrice方法,则存在一定的风险,可能影响其他地方的调用结果*/
public class ComputerDiscounts extends Computer{public ComputerDiscounts(Integer id, String name, Double price) {super(id, name, price);}public Double getOrginPrice(){return super.getPrice();}public Double getPrice(){return super.getPrice()*0.88;}
}

依赖倒置原则示例

1、还是以商品类为例,先创建学生类

package com.ruoyi.demo.designPrinc.dip;public class Student {public void buyComputer(){System.out.println("学生购买了电脑");}public void buyBook(){System.out.println("学生购买了书籍");}
}

调用学生类的购买电脑和购买书籍的方法

package com.ruoyi.demo.designPrinc.dip;public class dipTest {public static void main(String[] args) {Student student = new Student();student.buyComputer();student.buyBook();}
}

学生购买了电脑和书籍,如果还要购买别的商品。这时候因为业务扩展,要从低层到高层(调用层)依次修改代码。

在Student类中添加buyPen()方法,在高层调用方也要追加调用。这样一来,系统发布之后很不稳定,可能导致

意想不到的风险。

下面优化代码

创建一个商品的抽象IGoods接口,抽离出购买方法

package com.ruoyi.demo.designPrinc.dip;public interface IGoods {void buy();
}

然后编写购买电脑类

package com.ruoyi.demo.designPrinc.dip;public class BuyComputer implements IGoods{@Overridepublic void buy() {System.out.println("学生买了电脑");}
}

再编写购买书籍类

package com.ruoyi.demo.designPrinc.dip;public class BuyBook implements IGoods{@Overridepublic void buy() {System.out.println("学生买了书籍");}
}

修改Student

package com.ruoyi.demo.designPrinc.dip;public class Student {public void buy(IGoods goods){goods.buy();}
}

最后修改调用方代码

package com.ruoyi.demo.designPrinc.dip;public class dipTest {public static void main(String[] args) {Student student = new Student();student.buy(new BuyComputer());student.buy(new BuyBook());}
}

这时,当再有新的业务,比如购买手机等扩展时,只需要新建一个类,通过传参的方式告诉Student,

而不需要修改底层代码。这种方式也叫依赖注入。注入的方式还有构造器注入和Setter方法。

单一职责原则示例

用商品举例,自营商品可以无条件退货,代理商品需要协商退货。创建商品类

package com.ruoyi.demo.designPrinc.srp;public class Goods {public void buy(String goodsType){if("自营".equals(goodsType)){System.out.println(goodsType+"商品可以无条件退货");}else {System.out.println(goodsType+"商品需要协商退货");}}
}

调用代码

package com.ruoyi.demo.designPrinc.srp;public class srpTest {public static void main(String[] args) {Goods goods = new Goods();goods.buy("自营");goods.buy("代理");}
}

上面逻辑中,商品Goods类处理两种逻辑,假如需要对商品进行活动促销,两种类型商品的活动逻辑不一样,

必须修改代码,而修改代码势必会相互影响。所以对职责进行解耦。

分别创建两个类

proprietaryGoods:

package com.ruoyi.demo.designPrinc.srp;/*** 自营商品*/
public class proprietaryGoods {public void buy(String goodsType){System.out.println(goodsType+"商品需要协商退货");}
}

agentGoods :

package com.ruoyi.demo.designPrinc.srp;/*** 代理商品*/
public class agentGoods {public void buy(String goodsType){System.out.println(goodsType+"商品可以无条件退货");}
}

调用代码修改

package com.ruoyi.demo.designPrinc.srp;public class srpTest {public static void main(String[] args) {proprietaryGoods proprietaryGoods = new proprietaryGoods();proprietaryGoods.buy("自营");agentGoods agentGoods = new agentGoods();agentGoods.buy("代理");}
}

业务发展,要增加会员VIP业务。VIP可以获取商品优惠券,普通会员只能获取商品基本价格。

所以在控制商品上有两个职责,可以将展示的职责和管理的职责分开,实现同一个抽象依赖。

设计顶层接口IGoods

package com.ruoyi.demo.designPrinc.srp;import java.util.List;public interface IGoods {//获取商品价格String getGoodsPrice();//获取商品优惠券List<Double> getCoupons();//购买商品void buyGoods();//商品退货void returnGoods();
}

将这个接口拆分成两个接口

IGoodsInfo

package com.ruoyi.demo.designPrinc.srp;import java.util.List;public interface IGoodsInfo {//获取商品价格String getGoodsPrice();//获取商品优惠券List<Double> getCoupons();
}

IGoodsManager

package com.ruoyi.demo.designPrinc.srp;public interface IGoodsManager {//购买商品void buyGoods();//商品退货void returnGoods();
}

接口隔离原则示例

商品类接口

package com.ruoyi.demo.designPrinc.isp;public interface IGoods {void eat();void drink();void wear();
}

有能吃的、能喝的、能穿的。

食品类

package com.ruoyi.demo.designPrinc.isp;public class FoodGoods implements IGoods{@Overridepublic void eat() {}@Overridepublic void drink() {}@Overridepublic void wear() {}
}

衣物类

package com.ruoyi.demo.designPrinc.isp;public class ClothesGoods implements IGoods{@Overridepublic void eat() {}@Overridepublic void drink() {}@Overridepublic void wear() {}
}

所以,如果按照上面的进行设计,那么食品类的wear只能空着,衣物类的eat和drink也只能空着。

所以分别设计吃、喝、穿三个接口

package com.ruoyi.demo.designPrinc.isp;public interface IEatGoods {void eat();
}package com.ruoyi.demo.designPrinc.isp;public interface IDrinkGoods {void drink();
}package com.ruoyi.demo.designPrinc.isp;public interface IWearGoods {void wear();
}

然后食品类是需要实现吃、喝接口即可

package com.ruoyi.demo.designPrinc.isp;public class FoodGoodsNew implements IEatGoods,IDrinkGoods{@Overridepublic void drink() {}@Overridepublic void eat() {}
}

迪米特原则示例

老板要采购员统计符合条件的商品数量。

商品类

package com.ruoyi.demo.designPrinc.lod;public class Goods {
}

采购员

package com.ruoyi.demo.designPrinc.lod;import java.util.List;public class Buyer {public void checkNumberOfGoods(List<Goods> goodsList){System.out.println("符合条件的商品数量为:"+goodsList.size());}
}

老板

package com.ruoyi.demo.designPrinc.lod;import java.util.ArrayList;
import java.util.List;public class Boss {public void commandCheckNumber(Buyer buyer){//模拟老板一页一页往下翻页,采购员实时统计List<Goods> goodsList = new ArrayList<>();for (int i = 0; i < 100; i++) {goodsList.add(new Goods());}buyer.checkNumberOfGoods(goodsList);}
}

测试代码

package com.ruoyi.demo.designPrinc.lod;public class lodTest {public static void main(String[] args) {Boss boss = new Boss();Buyer buyer = new Buyer();boss.commandCheckNumber(buyer);}
}

根据迪米特原则,Boss只想要结果,不需要跟商品直接交流。但是采购员统计需要引用商品对象。

所以在Boss类中就出现了Goods商品类。

只需要将Boss类中的Goods类交给采购员,使Boss与Goods不再有关联。

Buyer:

package com.ruoyi.demo.designPrinc.lod;import java.util.ArrayList;
import java.util.List;public class Buyer {public void checkNumberOfGoods(){List<Goods> goodsList = new ArrayList<>();for (int i = 0; i < 100; i++) {goodsList.add(new Goods());}System.out.println("符合条件的商品数量为:"+goodsList.size());}
}

Boss:

package com.ruoyi.demo.designPrinc.lod;public class Boss {public void commandCheckNumber(Buyer buyer){buyer.checkNumberOfGoods();}
}

里氏替换原则示例

新建鸟类,有飞行速度属性,可以根据飞行距离和飞行速度计算飞行时间

package com.ruoyi.demo.designPrinc.lsp;/*** 鸟类*/
public class Bird {//飞行速度double flySpeed;public double getFlySpeed() {return flySpeed;}public void setFlySpeed(double flySpeed) {this.flySpeed = flySpeed;}public double getFlyTime(double distance){return (distance/flySpeed);}
}

新建燕子类,继承自鸟类

package com.ruoyi.demo.designPrinc.lsp;/*** 燕子类*/
public class Swallow extends Bird{}

新建企鹅类,继承自鸟类

package com.ruoyi.demo.designPrinc.lsp;/*** 企鹅类*/
public class Penguin extends Bird{public void setFlySpeed(double speed){flySpeed = 0;}
}

因为企鹅不会飞,所以设置其飞行速度为0

计算飞行时间业务类

package com.ruoyi.demo.designPrinc.lsp;public class lspTest {public static void main(String[] args) {Bird swallow = new Swallow();swallow.setFlySpeed(100);Bird penguin = new Penguin();penguin.setFlySpeed(100);try {System.out.println("燕子飞行50公里耗时:"+swallow.getFlyTime(50));System.out.println("企鹅飞行50公里耗时:"+penguin.getFlyTime(50));}catch (Exception e){System.out.println("出错:"+e.getMessage());}}
}

上面的示例中,因为企鹅不具备飞行能力,重写了鸟类的速度方法,违背了里氏替换原则,所以当计算

企鹅的飞行时间时出现了除数不能为0的错误。

将上面的继续关系修改为,增加一个动物类,描述动物的普遍通用行为,比如计算时间和速度,鸟类和企鹅分别继承动物类,

燕子继承鸟类。这样燕子能计算飞行时间,企鹅能计算奔跑时间,同时又避免了重写父类的方法,符合里氏替换原则。

合成复用原则示例

以数据库操作添加商品为例,创建数据库连接类

public class DBConnection {public String getConnection(){return "Mysql 数据库连接";}
}

然后创建GoodsDao

package com.ruoyi.demo.designPrinc.carp;public class GoodsDao {private DBConnection dbConnection;public void setDbConnection(DBConnection dbConnection) {this.dbConnection = dbConnection;}public void addGoods(){String connection = dbConnection.getConnection();System.out.println("使用连接"+connection+"增加商品成功");}
}

上面的DBConnection还不是一种抽象,不便于系统扩展。目前的系统支持Mysql数据库连接。

后续业务扩展,需要支持Oracle数据库连接。

如果直接在DBConnection中增加对Oracle数据库的支持,会违背开闭原则。

可以在不修改Dao代码的前提下,而将DBConnection修改为abstract的。

package com.ruoyi.demo.designPrinc.carp;public abstract class DBConnection {public abstract  String getConnection();
}

然后将Mysql的逻辑抽离

package com.ruoyi.demo.designPrinc.carp;public class MySqlConnection extends DBConnection{@Overridepublic String getConnection() {return "Mysql数据库连接";}
}

再创建Oracle连接

package com.ruoyi.demo.designPrinc.carp;public class OracleConnection extends DBConnection{@Overridepublic String getConnection() {return "Oracle 数据库连接";}
}

具体选择交给应用层。

软件架构设计原则-开闭、依赖倒置、单一职责、接口隔离、迪米特、里氏替换、合成复用,附Java语言示例讲解相关推荐

  1. 带你认识六种设计原则(开闭原则、里氏代换原则、依赖倒转原则....)

    前言 1. 设计原则 1.1. 开-闭原则 1.2. 里氏代换原则 1.3. 依赖倒转原则 1.4. 接口隔离原则 1.5. 合成/聚合原则 1.6. 迪米特法则 前言 学习设计模式之前先要了解其中的 ...

  2. 六大设计原则--开闭原则

    定义 software entities like classes, modules and functions should be open for extension but closed for ...

  3. 六大设计原则-开闭原则

    1.开闭原则        2.接口隔离原则 3.依赖倒置原则 4.迪米特原则             5.里氏替换原则     6.单一职责原则 什么是开闭原则? 开闭原则就十个字:对扩展开放,对修 ...

  4. 设计模式-设计原则-开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段...

    开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成,相互补充,目标一致,只是分析问题时所站角度不同而已. 转载于:https://www.cnblogs.com/jiangtao12 ...

  5. 软件设计原则-开闭原则

    1.1 开闭原则 对扩展开放,对修改关闭.在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果.简言之,是为了使程序的扩展性好,易于维护和升级. 想要达到这样的效果,我们需要使用接口和 ...

  6. 软件设计原则——开闭原则

    开闭原则 对扩展开放,对修改关闭. 在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果.简言之,是为了使程序的扩展性好,易于维护和升级. 想要达到这样的效果,我们需要使用接口和抽象类 ...

  7. C++设计原则——开闭原则(持续更新中)

    系列文章目录 C++开闭原则 C++迪米特法则 文章目录 系列文章目录 前言 一.开闭原则到底有什么用? 二.开闭原则有什么特点 前言 在一个新人刚入编程这一行的时候,可能在正式参加到一个项目之前跟着 ...

  8. Java七大设计原则 - 开闭原则

    一. 什么是开闭原则(Open Closed Principle)? 开闭原则(Open Closed Principle,OCP)又称为ocp原则, 由勃兰特·梅耶(Bertrand Meyer)提 ...

  9. Java设计原则——开闭原则

    我们一定要给自己提出这样的任务:第一,学习,第二是学习,第三还是学习. 学习从来无捷径,循序渐进登高峰. 目录 一.基本介绍 二.应用实例 1.示例一 2.示例二 部分笔记来源于尚硅谷设计模式视频教学 ...

最新文章

  1. QQ拼音在中文输入下默认英文标点
  2. js中计算中文长度方法
  3. 计算机操作系统(八)---虚拟存储器
  4. Spring 开启Annotation context:annotation-config 和 context:component-scan诠释及区别
  5. 为outlook增加“邮件召回”功能
  6. Scala中zip或者zipWithIndex的计数器用法
  7. 王道操作系统考研笔记——2.1.7 进程调度的时机、切换与过程、方式
  8. spring中aop事务
  9. 接口测试工具之抓包工具介绍
  10. AudioSource中的PlayClipAtPoint()和PlayOneShot()
  11. CarMaker试用版许可证申请与软件安装过程
  12. SQLyog数据库导出
  13. 电脑不显示无线连接网络连接服务器,连接wifi后显示无internet访问权限怎么解决...
  14. yolov5的混淆矩阵
  15. 用 C 语言编写的程序被称为,用c语言编写的程序被称为
  16. poj 1163经典DP
  17. c语言的三种基本结构——初学者一定要了解哦
  18. IC-CAD IC 设计流程及 EDA 工具
  19. 数据库扩容也可以如此丝滑,MySQL千亿级数据生产环境扩容实战
  20. WiFi 2.4GHz和5GHz的差别

热门文章

  1. java获取键盘整数_Java中从键盘输入多个整数的方法
  2. 电脑开机显示器不显示BIOS界面,直接进入系统解决办法
  3. Excel股票分析工具_每日连板清单
  4. 大牛教你怎么学习Java多进程,下载量瞬秒百万
  5. 操作系统实验七 地址映射与共享(哈工大李治军)
  6. 数据结构——栈(栈结构、栈面试题、栈的操作、栈结构实现、进制的转换)
  7. 通过cocos裁切图片,将大图分为小图
  8. Transfer Learning - Overview(详细讲解)
  9. HTML5仿苹果Siri动画js特效
  10. 苹果商店数据分析报告