当前位置: 首页 > news >正文

设计模式-基础篇(六大原则)

按照,单,开,里,依,接,迪的顺序,这是一篇详细介绍了六大设计原则的文章,以通俗,易懂的语言进行说明,并配套有对应的实例,代码尽可能的简单。非常适合新手入门或者老手回顾,复习。
也是我自己的一次对设计模式原则的回顾。

单一职责

所谓的单一职责,简单理解就是每一个类只负责一件事情,但是往往在开发中,我们会为了某些业务进行妥协,破坏了单一职责。比如说,一个商品新增的service,我们有可能在里面为了顺手,写上商品删除,修改等逻辑。最终,这个service就变成了商品处理的职责。
很多人会纠结在商品整体处理的层面上,这么写好像做到了单一职责,但是细化到增删改单个功能,又感觉破坏了单一职责。其实单一职责没有绝对的死板标准,核心判断逻辑是:一个类只有一个修改的理由。
通俗解释:只要这个类的所有功能,都是因为同一个业务需求变更需要修改,那就是符合单一职责的。不用过度钻牛角尖、过度拆分代码,设计原则的本质是为了简化开发,不用太过于条条框框,应该因地制宜、灵活使用。

代码说明

违反单一职责的例子
// 违背单一职责:一个类承担多个商品操作职责publicclassGoodsService{// 新增商品publicvoidaddGoods(){System.out.println("新增商品逻辑");}// 修改商品publicvoidupdateGoods(){System.out.println("修改商品逻辑");}// 删除商品publicvoiddeleteGoods(){System.out.println("删除商品逻辑");}}
符合单一职责的列子
// 只负责商品新增,仅新增需求变更时才会修改publicclassGoodsAddService{publicvoidaddGoods(){System.out.println("新增商品逻辑");}}// 只负责商品修改,仅修改需求变更时才会修改publicclassGoodsUpdateService{publicvoidupdateGoods(){System.out.println("修改商品逻辑");}}// 只负责商品删除,仅删除需求变更时才会修改publicclassGoodsDeleteService{publicvoiddeleteGoods(){System.out.println("删除商品逻辑");}}

补充说明,其实日常使用,在小型项目中,如果公司没有严格上的规范要求,为了简单省事儿,大部分情况下,大家都用第一种。
统一的 GoodsService(商品整体操作)只有「商品业务规则」这一个变更理由,也是符合单一职责的,完全可以因地制宜使用。

开闭原则

对扩展开放,对修改关闭。我的理解是本质上就是担心在修改原有代码的过程中,不小心改坏了原有的正常功能。因此,有新的业务需求,最好不要改动老代码,而是单独扩展新代码。
但是,这个原则在日常开发中,也不绝对。有时候,我们遇到一些很简单的修改,比如单纯替换一个常量、修改一段固定文案,这种简单改动只要细心核对,一般不会有什么大问题,不用刻意死板扩展。
而对于新增的、复杂的业务逻辑,则最好单独写一个方法、单独抽离逻辑进行调用,不要在原有成熟代码里进行大量编写、堆砌新逻辑。

代码说明

违反开发原则示例
// 原有成熟代码:基础商品折扣服务publicclassDiscountService{// 原有默认折扣:普通用户9折publicdoublegetDiscount(doubleprice){returnprice*0.9;}}// 新增需求:需要VIP用户8折折扣// 错误写法:直接修改原有成熟方法,修改原有代码,违背开闭原则publicclassDiscountService{publicdoublegetDiscount(doubleprice,StringuserType){// 原有逻辑if("normal".equals(userType)){returnprice*0.9;}// 新增逻辑:直接塞到老方法中,改动原有代码if("vip".equals(userType)){returnprice*0.8;}returnprice;}}
符合开闭原则的写法
// 原有成熟代码:完全不改动、保留原有逻辑publicclassDiscountService{publicdoublegetDiscount(doubleprice){returnprice*0.9;}}// 新增VIP折扣业务:单独扩展新类,拓展新功能publicclassVipDiscountServiceextendsDiscountService{// 扩展新的VIP折扣逻辑,不修改父类原有代码publicdoublegetVipDiscount(doubleprice){returnprice*0.8;}}

这样做的好处就是,已经上线的代码我们没有改动过,不影响原有功能。我的建议是,小范围更改可以灵活变动,但是复杂业务一定要使用开闭原则。

里氏替换原则

这个名称听起来有点奇怪,但我们不用关注他的名字,只需要牢牢记住,所谓的里氏替换,就是父类可以被使用的地方,子类也可以完全替代父类使用,并且不会超出父类的能力限定范围,程序不会出现异常。
这里有一个核心重点:子类重写方法后,允许个性化实现,但不能违背父类的行为契约。
所谓的契约其实就是父类对外承诺的能力。比如父类鸟类承诺自己可以飞行,这就是对外的契约。子类麻雀、老鹰可以各自实现不一样的飞行效果,这是正常的个性化重写,完全符合规则;但如果子类鸵鸟继承鸟类,却重写方法表示不会飞、直接报错,就违背了父类的契约,彻底破坏了里氏替换原则。
子类可以和父类、其他子类的执行细节不一样,但是父类能做到的事,子类必须也能做到,不能翻车、报错、失效。

代码说明

反面例子
// 父类:鸟类,对外契约:所有鸟都会飞classBird{publicvoidfly(){System.out.println("鸟儿可以正常飞行");}}// 子类:鸵鸟(违背里氏替换)classOstrichextendsBird{// 破坏父类契约:父类会飞,子类不会飞@Overridepublicvoidfly(){// 空实现/抛异常,功能失效System.out.println("鸵鸟不会飞行");}}// 测试方法:接收父类BirdpublicclassBirdTest{publicstaticvoidletFly(Birdbird){bird.fly();}publicstaticvoidmain(String[]args){// 传入父类正常运行letFly(newBird());// 传入子类,逻辑失效,违背里氏替换letFly(newOstrich());}}
正面例子
// 父类:鸟类,契约:所有鸟都会飞classBird{publicvoidfly(){System.out.println("鸟儿可以正常飞行");}}// 子类:麻雀(合规重写,个性化实现)classSparrowextendsBird{@Overridepublicvoidfly(){System.out.println("麻雀在低空自由飞行");}}// 子类:老鹰(合规重写,个性化实现)classEagleextendsBird{@Overridepublicvoidfly(){System.out.println("老鹰在高空展翅飞行");}}// 测试方法:任意子类都可以完美替换父类publicclassBirdTest{publicstaticvoidletFly(Birdbird){bird.fly();}publicstaticvoidmain(String[]args){letFly(newBird());letFly(newSparrow());letFly(newEagle());}}

所以,里氏替换,重点其实就是父类的定义。写好前期的规范,约束,后期大家都进行遵守,达到子类,父类,完美替换的效果。

依赖倒置原则

很多人会混淆继承关系和依赖关系,一个常见误区就是依赖倒置的核心不是父子类之间的依赖,而是项目中所有的高层模块、低层模块,都依赖抽象(接口/抽象类),不依赖具体的实现类。

网上有一句话:子类依赖于父类,父类不依赖于子类。这句话本身就是开发通用准则,和父类是不是抽象类、接口没有关系,不存在对错之分,任何时候都不允许父类依赖子类。

抽象是固定的规范,具体实现是多变的。我们写代码要盯着固定的规范写,不要盯着具体的业务实现写。这样后续改需求、换实现方式的时候,不用改动核心代码,更加灵活。

代码说明

反面例子
// 具体实现类:微信支付(底层具体功能)publicclassWechatPay{publicvoidpay(){System.out.println("微信支付成功");}}// 高层业务模块:直接依赖具体实现类 WechatPaypublicclassPayService{// 死死绑定具体类,无法灵活替换privateWechatPaypay=newWechatPay();publicvoiddoPay(){pay.pay();}}
正面例子
// 抽象规范:支付接口(固定不变的抽象层)publicinterfacePay{voidpay();}// 底层实现1:微信支付(可变的具体实现)publicclassWechatPayimplementsPay{@Overridepublicvoidpay(){System.out.println("微信支付成功");}}// 底层实现2:支付宝支付(后续可无限扩展)publicclassAliPayimplementsPay{@Overridepublicvoidpay(){System.out.println("支付宝支付成功");}}// 高层业务模块:只依赖抽象接口,不依赖具体类publicclassPayService{// 面向抽象编程,灵活替换任意支付实现privatePaypay;publicPayService(Paypay){this.pay=pay;}publicvoiddoPay(){pay.pay();}}// 测试调用publicclassPayTest{publicstaticvoidmain(String[]args){// 随时切换实现,无需改核心代码newPayService(newWechatPay()).doPay();newPayService(newAliPay()).doPay();}}

依赖倒置,高层和底层都进行依赖抽象。一开始就需要定义好抽象约束,并且,要使用记住,实现依赖于抽象,抽象绝不能绑定具体的实现。

接口隔离原则

所谓的接口隔离,其实与单一职责有点像,但是也有很大的不同。单一职责有时候需要我们主观上去区分这个职责是否单一,就比如有些人认为,商品处理类,处理商品增删改查,都包含在里面,属于单一职责。而有些人则认为,增删改查也可以抽离出来做单一职责,这个相对比较主观,仁者见仁智者见智。
而接口隔离和它最大的不同就是,接口隔离是客观的、有明确标准的。核心规则就是:一个类如果实现了某个接口,就不应该存在不需要实现的空方法、无效方法。如果一个接口里有很多无用方法,被迫让实现类空实现,就说明这个接口太臃肿,需要拆分。

代码说明

反面例子
// 臃肿、未拆分的大接口publicinterfaceGoodsOperation{// 商品新增voidaddGoods();// 商品修改voidupdateGoods();// 商品删除voiddeleteGoods();// 商品查询voidqueryGoods();}// 只需要新增功能的实现类,被迫实现所有无用方法publicclassGoodsAddImplimplementsGoodsOperation{@OverridepublicvoidaddGoods(){System.out.println("执行商品新增");}// 无用方法,被迫空实现(严重违背接口隔离)@OverridepublicvoidupdateGoods(){}@OverridepublicvoiddeleteGoods(){}@OverridepublicvoidqueryGoods(){}}
正面例子
// 拆分后:单一功能细粒度接口publicinterfaceGoodsAdd{voidaddGoods();}publicinterfaceGoodsUpdate{voidupdateGoods();}publicinterfaceGoodsDelete{voiddeleteGoods();}publicinterfaceGoodsQuery{voidqueryGoods();}// 实现类按需实现,无冗余、无空方法publicclassGoodsAddImplimplementsGoodsAdd{@OverridepublicvoidaddGoods(){System.out.println("执行商品新增");}}// 需要多个功能可多实现接口,灵活组合publicclassGoodsFullImplimplementsGoodsAdd,GoodsUpdate,GoodsDelete,GoodsQuery{@OverridepublicvoidaddGoods(){System.out.println("执行商品新增");}@OverridepublicvoidupdateGoods(){System.out.println("执行商品修改");}@OverridepublicvoiddeleteGoods(){System.out.println("执行商品删除");}@OverridepublicvoidqueryGoods(){System.out.println("执行商品查询");}}

这样做的好处就是,自己实现自己的,但是,这个例子其实不是很恰当,因为大部分情况下,如此简单的业务逻辑,我们会用上面哪个反面例子来写。

迪米特法则

这个名字也有点奇怪,但是换一个名字,大家就知道了。所谓的迪米特法则,其实就是最少知道原则。学过java的同学知道,在Java的三大特性中,有一个很重要的特性,就是封装。调用封装的方法,我们不需要管封装里面是怎么样处理的,只需要知道,这个方法需要的输入参数是什么,会产生什么样的结果即可。
在这个基础上,去理解迪米特法则就很简单了:调用方不需要了解被调用方的内部逻辑、代码细节,只需要通过公开的方法完成调用,知道最终的执行效果就行。尽量减少类与类之间的关联,知道的越少,代码耦合度越低,越不容易出bug。

代码说明

反面例子
// 学生类publicclassStudent{// 公开属性(暴露内部细节)publicStringname;publicintage;}// 老师类(严重违背迪米特法则)publicclassTeacher{// 直接访问学生的内部属性,过度知道对方细节publicvoidshowStudentInfo(Studentstudent){System.out.println("学生姓名:"+student.name);System.out.println("学生年龄:"+student.age);}}
正面例子
// 学生类:封装内部细节,只暴露公开方法publicclassStudent{// 私有内部属性,禁止外部访问privateStringname;privateintage;publicStudent(Stringname,intage){this.name=name;this.age=age;}// 仅对外提供公开方法,隐藏内部细节publicStringgetStudentInfo(){return"学生姓名:"+name+",学生年龄:"+age;}}// 老师类:最少知道,只调用公开方法publicclassTeacher{publicvoidshowStudentInfo(Studentstudent){// 只拿结果,不问过程,不碰内部细节System.out.println(student.getStudentInfo());}}// 测试调用publicclassTest{publicstaticvoidmain(String[]args){Studentstudent=newStudent("张三",18);newTeacher().showStudentInfo(student);}}

简单来说,就是自己处理自己的,调用方只需要知道调用可以达到什么样的效果即可,而不是帮助处理非自己的属性,变量。

以上,就是我对设计模式六大原则的全部理解。如今 AI 发展迅速,确实能帮我们快速生成代码、完成基础开发,但我始终认为,AI 永远需要人来主导、驱动。

AI 可以实现代码,却不具备业务前瞻性,无法预判未来的需求迭代与代码隐患。实际开发中,业务需求多变是常态,如果完全依赖 AI 重写代码,不仅频繁消耗开发时间,也会造成大量无效资源损耗。

所以,真正高效的编码方式是:人理清业务逻辑、提前预判迭代风险、遵循合理的设计原则,再借助 AI 辅助开发。人机相辅相成,才能让代码更稳健、拓展性更强,在复杂的业务迭代中游刃有余,在编码的道路上稳步前行。

http://www.cnnetsun.cn/news/4216879.html

相关文章:

  • 现场活动画面组织控制及抽奖的使用疑难问题汇编
  • 论多源异构数据集成与数据架构设计
  • 2026年烟台做智慧排水监测系统的公司前10名有哪些?
  • PyWenCai:用 Python 调用同花顺问财,5 分钟搭好金融数据采集管道
  • 鸿蒙 ArkTS 实战|二元一次方程组应用:两直线交点求解 + 三种解的可视化判定
  • 144、影像算法DSP/NPU Offloading——降噪/超分/语义分割算子在高通/联发科/海思平台的部署选择
  • Edge浏览器JavaScript脚本开发全攻略:从用户脚本到扩展与自动化
  • 后端转Agent开发已上岸,我说说怎样学更容易找到工作 !
  • Python装饰器深度解析:从函数对象到实战应用
  • 071、6D位姿估计基础与PPF:点对特征的刚体配准原理
  • 从QClaw到AI Agent:揭秘GUI自动化与安全实践
  • 347H耐中高温不锈钢材料347H耐热螺丝347H不锈钢螺柱347H不锈钢螺栓347H螺丝347H螺母347H不锈钢螺丝347H牙棒-东明天津紧固件
  • 基于TipTap构建AI协同写作应用:架构设计与实战指南
  • 年薪40W起步的AI测开岗,面试官到底在考什么?
  • MySQL 插入冲突了怎么办?两种处理方式入门笔记
  • Z型钢直销厂家排行榜新鲜出炉,这份榜单帮你避开选购陷阱
  • 微软常用运行库合集(Visual C++)安装与使用教程
  • 一个搜索框接通 7 大音乐平台:Listen1 免费音乐聚合扩展完整上手攻略
  • Windows + WSL 环境下使用 LangGraph PostgresSaver 连接 PostgreSQL 缓慢的问题
  • Android开发面试全攻略:从基础到架构设计
  • Pytest自动化测试核心技术与面试高频考点解析
  • 量化交易:技术不是决定一切的最重要问题!
  • EC853一键开关机芯片分享硬件选型[特殊字符]12V/24V不用改板!
  • 信息管理硕士论文降AI教程:信管专业研究生论文AIGC超标4.8元知网一次达标完整操作指南
  • SQL Server数据插入性能优化:从单条到海量的七种方法对比
  • 基于SpringBoot的药品购买管理系统的设计与实现(源码+LW+调试文档)
  • Twisted Deferred 与 asyncio 的双向桥接
  • 【会议征稿通知 | 郑州轻工业大学主办 | JPCS出版 | EI 、Scopus稳定检索】第三届航空航天、机械与材料工程国际学术会议 (AMME 2026)
  • 基于SpringBoot快递管理系统设计和实现(源码+LW+调试文档)
  • 制造业生产质量管理系统的关键要素