按照单开里依接迪的顺序这是一篇详细介绍了六大设计原则的文章以通俗易懂的语言进行说明并配套有对应的实例代码尽可能的简单。非常适合新手入门或者老手回顾复习。也是我自己的一次对设计模式原则的回顾。单一职责所谓的单一职责简单理解就是每一个类只负责一件事情但是往往在开发中我们会为了某些业务进行妥协破坏了单一职责。比如说一个商品新增的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{// 死死绑定具体类无法灵活替换privateWechatPaypaynewWechatPay();publicvoiddoPay(){pay.pay();}}正面例子// 抽象规范支付接口固定不变的抽象层publicinterfacePay{voidpay();}// 底层实现1微信支付可变的具体实现publicclassWechatPayimplementsPay{Overridepublicvoidpay(){System.out.println(微信支付成功);}}// 底层实现2支付宝支付后续可无限扩展publicclassAliPayimplementsPay{Overridepublicvoidpay(){System.out.println(支付宝支付成功);}}// 高层业务模块只依赖抽象接口不依赖具体类publicclassPayService{// 面向抽象编程灵活替换任意支付实现privatePaypay;publicPayService(Paypay){this.paypay;}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.namename;this.ageage;}// 仅对外提供公开方法隐藏内部细节publicStringgetStudentInfo(){return学生姓名name学生年龄age;}}// 老师类最少知道只调用公开方法publicclassTeacher{publicvoidshowStudentInfo(Studentstudent){// 只拿结果不问过程不碰内部细节System.out.println(student.getStudentInfo());}}// 测试调用publicclassTest{publicstaticvoidmain(String[]args){StudentstudentnewStudent(张三,18);newTeacher().showStudentInfo(student);}}简单来说就是自己处理自己的调用方只需要知道调用可以达到什么样的效果即可而不是帮助处理非自己的属性变量。以上就是我对设计模式六大原则的全部理解。如今 AI 发展迅速确实能帮我们快速生成代码、完成基础开发但我始终认为AI 永远需要人来主导、驱动。AI 可以实现代码却不具备业务前瞻性无法预判未来的需求迭代与代码隐患。实际开发中业务需求多变是常态如果完全依赖 AI 重写代码不仅频繁消耗开发时间也会造成大量无效资源损耗。所以真正高效的编码方式是人理清业务逻辑、提前预判迭代风险、遵循合理的设计原则再借助 AI 辅助开发。人机相辅相成才能让代码更稳健、拓展性更强在复杂的业务迭代中游刃有余在编码的道路上稳步前行。