宝山企业做网站,做户外商城网站,wordpress归档页,网站建设综合训练报告0.个人感悟
设计原则类似修真世界里的至高法则#xff0c;万法的源头。遵守法则造出的术法具有能耗低、恢复快、自洽性高等优点#xff0c;类似遵守设计原则设计的出的程序#xff0c;具有很多优点设计原则从不同的角度对软件设计提供了约束和指导。其中开闭原则、依赖倒置让…0.个人感悟设计原则类似修真世界里的至高法则万法的源头。遵守法则造出的术法具有能耗低、恢复快、自洽性高等优点类似遵守设计原则设计的出的程序具有很多优点设计原则从不同的角度对软件设计提供了约束和指导。其中开闭原则、依赖倒置让人印象深刻。我第一次重构一个模块时对开闭原则有了一丝感悟设计原则的核心思想: 对象内部高内聚对象之间交互松耦合变化也不变隔离开建议有条件读原著。写博客过程中这些原则的英文都是手打出来的感觉确实原汁原味过一遍和听别人说出来不一样最后一点是写给自己。这个记录是目前写的最久的一次了上次写这么久文档还是参加公司代码评选。主要是设计原则本来就很重要网上的教程特别是举的例子也参差不齐想着结合自己所学用一个大场景来覆盖所有原则。事实上还是能力有限中间也想过放弃反复纠结中还是坚持下来了。不要害怕不完美不要害怕被喷错就改不要总想着一步到位可以一层一层目标实现谢谢坚持谢谢老婆的支持1.概念设计模式原则是程序员在编程时应当遵循的原则也是各种设计模式的基础。2.作用指导程序设计也是具体的设计模式遵循的规则同[[[设计模式学习(1) 概述]]]中讲到的也是有助于实现软件开发过程中1(高内聚低耦合)核4性(复用性 可读性 可扩展性 稳定性)个人修行的心法3. 分类说起设计模式分类网上流传的有5大原则、6大、7大原则那到底是哪些出处又是哪些呢。查阅资料发现其实也没有一个明确的官方说法只是不同时期不同的作者提出。每个原则的提出都有自己的价值后面会按7大原则就进行学习solid族5大设计原则 。罗伯特·C·马丁在2000年左右撰写了一系列文章来阐述这些原则并最终在2003年的论文《Design Principles and Design Patterns》中将其整合并命名为“SOLID”S 单一职责原则O 开闭原则L 里氏替换原则I 接口隔离原则D 依赖倒置原则迪米特法则(最小知道原则)**伊恩·霍兰德 **等在1987年美国东北大学在项目研究报告《The Demeter Project: Aspect-Oriented Programming》中提出合成复用原则四人帮(Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)在1994年出版的《设计模式可复用面向对象软件的基础》中提到。这本书被誉为设计模式圣经首次系统地提出了23中经典设计模式。也是这个系列文章重要的参考。建议大家有条件去读一读4.详细介绍4.1 单一职责原则(Single Responsibility Principle,SRP)4.1.1定义A class should have one, and only one, reason to change.字面意思一个类应该只有一个引起变化的原因理解:一个类应该只负责一种职责。如果负责多个职责那么当一个职责变化时可能影像其它职责变化进而引起整个类的变化。4.1.2 好处某个原则的优点、包括违背原则的以及后续其它设计原则的介绍都围绕软件开发的1核4性来说明。这也是为什么我写文章时在专门在开篇里提到“软件开发过程遇到的问题和想达到的目标”。因为设计模式包括设计与原则都是绕不开这个目标。这里分析优缺点时只介绍突出的项。高内聚低耦合 职责粒度内聚代码职责分开了耦合也就低了稳定性 从原则的定义上就很明显地体现出将代码修改引起的变化控制到某个职责某个类中4.1.3 违背原则会存在的问题高内聚低耦合 违背单一职责原则结果上发现是反过来的类承担过多职责不内聚多个职责耦合到一块高耦合稳定性 多个职责耦合到一块修改一个职责就有影像其它职责的风险4.1.4 如何做这个要求设计人员有很强的分析、设计能力和经验能识别将系统中不同的职责分离开来并封装到不同的类或者模块中4.1.5 示例这里假设一个业务场景博客评论流程简化为 用户验证-评论内容校验-评论存储模型设计: 用户模块User 文章模块Article 评论模块Commmet示例:UserService和CommentService职责分开UserService负责用户验证CommmetService负责评论相关操作;而不是全放一个service中UserService:packageprinciple.blog.user.service;publicinterfaceUserService{booleancanPostComment();}CommentService:packageprinciple.blog.comment.service;importprinciple.blog.comment.model.Comment;importjava.util.List;publicinterfaceCommentService{voidpostComment(Stringcontent);voideditComment(StringcommentId,StringnewContent);voiddeleteComment(StringcommentId);voidreplyToComment(StringparentCommentId,Stringcontent);ListCommentgetReplies(StringcommentId);}模拟评论流程packageprinciple.blog.test.srp;importprinciple.blog.comment.model.Comment;importprinciple.blog.comment.service.CommentService;importprinciple.blog.user.service.UserService;publicclassSrpTest{privatefinalUserServiceuserService;privatefinalCommentServicecommentService;publicSrpTest(UserServiceuserService,CommentServicecommentService){this.userServiceuserService;this.commentServicecommentService;}voidsrp(){CommentcommentnewComment();// 1.用户校验booleancanPostCommentuserService.canPostComment();// 2.评论校验逻辑// 3.存储commentService.postComment(comment.getContent());// 4.其它逻辑}}4.2 开闭原则(Open-Closed Principle,OCP)4.2.1定义Software entities (class, modules, functions,etc.) should be open for extension, but closed for modification.字面意思软件实体类、模块、函数等应该对扩展开放对修改关闭。理解感觉这是最基础、最重要的设计原则。其它原则都是在为开闭原则服务并且在具体设计模式学习中尤为感受深刻软件设计时允许在不改变先手代码的情况下扩展功能。对扩展开放(提供方)修改关闭(使用放)4.2.2 好处高内聚低耦合 高内聚低耦合的代码易于扩展遵循开闭原则倒逼设计者考虑如何提高内聚降低耦合扩展性、稳定性 简直是量身定做原则的定义中就提到了扩展性。对修改关闭也就意味着改动不应该影像核心代码进而增强系统稳定性。4.2.3 违背原则会存在的问题高内聚低耦合 不遵循开闭原则很容易让人分不清哪些是变化的那些是不变的代码的内聚性会很差扩展性、稳定性 如果不按拓展方式修改有修改引入风险也增加测试的复杂度4.2.4 如何做这是一个系统性的设计问题。需要设计人员分析系统有哪些模块模块间的关系和通信协议模块中哪些是变化的那些是不变的利用抽象和多态实现4.2.5 示例还是博客评论业务需要对文本进行校验有不同的校验规则比如长度校验、敏感词校验等。设计时可以考虑抽取校验器接口统一管理后续新增校验规则只用实现校验器接口(对扩展开放)不用动使用方的代码(对修改关闭)校验器接口:packageprinciple.blog.comment.validation;importprinciple.blog.comment.model.Comment;publicinterfaceCommentFilter{booleanshouldFilter(Commentcomment);StringgetReason();}基于关键词的实现:packageprinciple.blog.comment.validation.impl;importprinciple.blog.comment.model.Comment;importprinciple.blog.comment.validation.CommentFilter;importjava.util.Set;publicclassKeywordFilterimplementsCommentFilter{privatefinalSetStringbannedKeywords;publicKeywordFilter(SetStringbannedKeywords){this.bannedKeywordsbannedKeywords;}OverridepublicbooleanshouldFilter(Commentcomment){Stringcontentcomment.getContent().toLowerCase();returnbannedKeywords.stream().anyMatch(content::contains);}OverridepublicStringgetReason(){return包含违禁关键词;}}基于链接校验的实现:packageprinciple.blog.comment.validation.impl;importprinciple.blog.comment.model.Comment;importprinciple.blog.comment.validation.CommentFilter;importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassExcessiveLinkFilterimplementsCommentFilter{privatefinalintmaxLinks;publicExcessiveLinkFilter(intmaxLinks){this.maxLinksmaxLinks;}OverridepublicbooleanshouldFilter(Commentcomment){Stringcontentcomment.getContent();longlinkCountcountLinks(content);returnlinkCountmaxLinks;}privatelongcountLinks(Stringcontent){PatternurlPatternPattern.compile(https?://[\\w./?]);MatchermatcherurlPattern.matcher(content);returnmatcher.results().count();}OverridepublicStringgetReason(){return包含过多链接;}}客户端代码packageprinciple.blog.test.ocp;importprinciple.blog.comment.model.Comment;importprinciple.blog.comment.validation.CommentFilter;importjava.util.List;publicclassOcpTest{// 注入所有过滤器实现privatefinalListCommentFilterfilters;publicOcpTest(ListCommentFilterfilters){this.filtersfilters;}voidocpTest(){CommentcommentnewComment();// 按个调用过滤器实现进行校验booleancanSavefilters.stream().noneMatch(item-item.shouldFilter(comment));// 其它逻辑实现}}4.3 里氏替换原则(Liskov Substitution Principle,LSP)4.3.1定义Subtypes must be substitutable for their base typses.字面意思: 子类型必须能够替换其基类型理解:详细说法: 如果对每个类型为 T1 的对象 o1都有类型为 T2 的对象 o2使得以 TI定义的所有程序P在所有的对象 o1 都代换成 o2 时程序P的行为没有发生变化那么类型T2是类型T1的子类型。换句话说所有引用基类的地方必须能透明地使用其子类的对象。出现这一原则的原因 继承的双刃性(这一点在韩老师的视频中讲的非常精准我之前学习这个原则时未get到这一点)继承的优点复用性: 父子族类有利于代码的组织和复用。这也是面向对象的优点。灵活性: 面向对象的三大特点封装、继承、多态。多态意味着代码可以更加灵活继承的弊端侵入性: 运行基类类的代码可以通过继承创建子类进而用子类替换父类如果子类中修改了基类功能(重写)那么势必会影响原代码功能。jdk中很多类都是final的。这也是这个原则提出背景。特别是无意识地修改对于系统的稳定性是具体风险。耦合性: 没错继承意味着耦合意味着你得清楚基类的功能和细节不合理地使用和扩展会有引入风险。4.3.2 好处稳定性 引用基类的地方可以透明地使用子类的对象可以保证使用基类的代码不会受到影响4.3.3 违背原则会存在的问题灵活性: 这一点其实算是优点。使用继承和多态有利于代码灵活性稳定性: 子类如果重写父类方法很容易影像父类代码正常运行4.3.4 如何做使用继承时在子类中尽量不要重写父类的方法对于变化的功能在基类中建议抽取成抽象方法为什么对于接口不会提出里氏替换原则因为接口定义的本来就是抽象的不会存在覆盖一说。如果类中某些行为在类族中存在变化建议提取抽象方法使用组合替代继承。后面合成复用原则会提到辩证使用继承和遵循里氏替换原则实际开发经验来说建议均衡继承的灵活性和风险性。不是一棒子打死一定要遵循里氏替换原则。比如Jdk中的Object基类其中toString方法子类中也经常覆盖。建议遵循的场景: 功能模块、容易变化可以考虑灵活性的场景: 基础模块不容易变化。比如你所负责模块是基础模块类似Jdk中基础类型很稳定你可以自行设计对外不暴露细节。4.3.5 示例4.3.5.1示例1:常用的例子正方形和长方形如果正方形继承长方形为了代码正确需要重写get、set。那么基类(长方形)的代码使用子类(正方形)的对象去替换就会出问题因为多态执行的是子类重写的方法。所以继承带来便捷的同时有这种修改风险。长方形:packageprinciple.liskovsubstitution.v1;publicclassRectangle{protectedintlength;protectedintwidth;publicintgetLength(){returnlength;}publicvoidsetLength(intlength){this.lengthlength;}publicintgetWidth(){returnwidth;}publicvoidsetWidth(intwidth){this.widthwidth;}publicintgetArea(){returnlength*width;}}正方形:packageprinciple.liskovsubstitution.v1;publicclassSquareextendsRectangle{OverridepublicvoidsetLength(intlength){this.lengthlength;this.widthlength;}OverridepublicvoidsetWidth(intwidth){this.lengthwidth;this.widthwidth;}}违背里氏替换原则packageprinciple.liskovsubstitution.v1;publicclassShapeClient{publicstaticvoidmain(String[]args){// 原始类型RectanglerectanglenewRectangle();rectangle.setLength(4);rectangle.setWidth(5);System.out.println(rectangle.getArea());// 子类rectanglenewSquare();rectangle.setLength(4);rectangle.setWidth(5);System.out.println(rectangle.getArea());}}那是不是一定要遵循里氏替换原则呢。其实也不能一棒子打死。比如jdk中的Object的toString()方法很多类型都重写了它。所以要结合业务区考虑如果是基础、稳定的模块做好封装也可以考虑继承。packageprinciple.liskovsubstitution.other;publicclassTypeTest{publicstaticvoidmain(String[]args){Objectobj1newObject();System.out.println(obj1.toString());Objectobj2newString();System.out.println(obj2.toString());}}对于长方形、正方形问题网上有很多讨论有认为继承没问题主要是没有提供构造方法导致太灵活;有干脆分成单独两个类;还有的采用合成复用方式让正方形组合长方形。我这里参考obj类设计采用这种设计提出一个公共基类抽象getArea()方法,两个类分别继承。packageprinciple.liskovsubstitution.v3;publicclassShape{intDEFAULT_AREA0;publicintgetArea(){returnDEFAULT_AREA;}}packageprinciple.liskovsubstitution.v3;publicclassSquareextendsShape{privateintwide;publicintgetWide(){returnwide;}publicvoidsetWide(intwide){this.widewide;}publicSquare(intwide){setWide(wide);}OverridepublicintgetArea(){returnwide*wide;}}packageprinciple.liskovsubstitution.v3;publicclassRectangleextendsShape{protectedintlength;protectedintwidth;publicRectangle(intlength,intwidth){this.lengthlength;this.widthwidth;}publicintgetLength(){returnlength;}publicvoidsetLength(intlength){this.lengthlength;}publicintgetWidth(){returnwidth;}publicvoidsetWidth(intwidth){this.widthwidth;}publicintgetArea(){returnlength*width;}}packageprinciple.liskovsubstitution.v3;importjava.util.ArrayList;importjava.util.List;publicclassShapeClient{publicstaticvoidmain(String[]args){// 长方形RectanglerectanglenewRectangle(4,5);System.out.println(rectangle.getArea());// 正方形SquaresquarenewSquare(6);System.out.println(square.getArea());// 基类统一管理ListShapeshapeListnewArrayList(2);shapeList.add(rectangle);shapeList.add(square);intsumshapeList.stream().mapToInt(Shape::getArea).sum();System.out.println(sum);}}4.3.5.2 示例二还是博客评论业务举一个正面例子利用抽象遇到这样一个场景持久层可以根据策略切换采用内存实现或者数据库实现这里设计的话持久层抽象为接口有内存、数据库两个实现根据配置进行切换。子类覆写的是抽象方法就不会违背里氏替换原则packageprinciple.blog.comment.repository;importprinciple.blog.comment.model.Comment;importjava.util.List;importjava.util.Optional;publicinterfaceCommentStorage{voidsave(Commentcomment);OptionalCommentfindById(Stringid);ListCommentfindByArticleId(StringarticleId);voiddelete(Stringid);// 基础统计方法intcountByArticleId(StringarticleId);intcountByUserId(StringuserId);}packageprinciple.blog.comment.repository;importprinciple.blog.comment.model.Comment;importjava.util.List;importjava.util.Map;importjava.util.Optional;importjava.util.concurrent.ConcurrentHashMap;importjava.util.stream.Collectors;/** * 内存存储实现 */publicclassInMemoryCommentStorageimplementsCommentStorage{privatefinalMapString,CommentstoragenewConcurrentHashMap();Overridepublicvoidsave(Commentcomment){storage.put(comment.getId(),comment);}OverridepublicOptionalCommentfindById(Stringid){returnOptional.ofNullable(storage.get(id));}OverridepublicListCommentfindByArticleId(StringarticleId){returnstorage.values().stream().filter(comment-articleId.equals(comment.getArticleId())).collect(Collectors.toList());}Overridepublicvoiddelete(Stringid){storage.remove(id);}OverridepublicintcountByArticleId(StringarticleId){return(int)storage.values().stream().filter(comment-articleId.equals(comment.getArticleId())).count();}OverridepublicintcountByUserId(StringuserId){return(int)storage.values().stream().filter(comment-userId.equals(comment.getAuthorId())).count();}}packageprinciple.blog.comment.repository;importprinciple.blog.comment.model.Comment;importjava.util.List;importjava.util.Optional;/** * 数据库存储实现 */publicclassDatabaseCommentStorageimplementsCommentStorage{// 使用JDBCOverridepublicvoidsave(Commentcomment){}OverridepublicOptionalCommentfindById(Stringid){returnOptional.empty();}OverridepublicListCommentfindByArticleId(StringarticleId){returnList.of();}Overridepublicvoiddelete(Stringid){}OverridepublicintcountByArticleId(StringarticleId){return0;}OverridepublicintcountByUserId(StringuserId){return0;}}packageprinciple.blog.test.lsp;importprinciple.blog.comment.repository.CommentStorage;importprinciple.blog.comment.repository.DatabaseCommentStorage;importprinciple.blog.comment.repository.InMemoryCommentStorage;publicclassLspTest{voidlsp(){// 仓存使用抽象定义。就不会存在子类继承后覆盖原来逻辑的场景CommentStoragestorage;// 仓存层可以根据配置切换内存实现还是数据库storagenewInMemoryCommentStorage();storagenewDatabaseCommentStorage();}}4.4 接口隔离原则(Interfance Segregation Principle,ISP)4.4.1定义Clients should not be forced to depend on methonds they do not use.字面意思:客户端不应该被迫依赖于它们不使用的方法理解:一个类对另一个类的依赖应该建立在最小的接口上接口隔离原则可以辅助单一职责原则。单一职责注重的是单个类职责的组织接口隔离注重的是依赖类职责的隔离。一个被依赖的类可以按被需要的功能来组织职责。4.4.2 好处高内聚低耦合 被依赖的类按需要去聚合职责提高内聚; 不依赖不需要的方法降低了类与类之间的耦合稳定性 同单一职责原则。将引起类变化的因素控制到最小。4.4.3 违背原则会存在的问题高内聚低耦合 提供过多的方法提供方功能不够内聚; 依赖不需要的方法增加了类与类之间的耦合稳定性 同单一职责原则。被依赖的类提供过多的方法那么这些方法在修改时有影响其它方法的风险进而影响使用方。4.4.4 如何做接口尽量小按需要去提供功能封装。封装细节只提供需要对外提供的方法辩证分析。结合具体业务设计接口。这个要求设计人员要有很强的分析设计经验4.4.5 示例还是博客评论业务业务变化评论要区分普通用户权限和管理员权限 管理员还可以审核设计的话考虑对原接口进行拆分通过组合方式对外提供不同服务将操作划分为3个粒度的接口:粒度接口1: 基本的评论操作packageprinciple.blog.comment.service.operations;/** * 细粒度接口1: 基本的评论操作 */publicinterfaceBasicCommentOperations{voidpostComment(Stringcontent);voideditComment(StringcommentId,StringnewContent);voiddeleteComment(StringcommentId);}细粒度接口2: 评论回复操作packageprinciple.blog.comment.service.operations;importprinciple.blog.comment.model.Comment;importjava.util.List;/** * 细粒度接口2: 评论回复操作 */publicinterfaceCommentReplyOperations{voidreplyToComment(StringparentCommentId,Stringcontent);ListCommentgetReplies(StringcommentId);}细粒度接口3: 评论审核操作仅审核员可用packageprinciple.blog.comment.service.operations;importprinciple.blog.comment.model.Comment;importjava.util.List;/** * 细粒度接口3: 评论审核操作仅审核员可用 */publicinterfaceCommentModerationOperations{voidapproveComment(StringcommentId);voidrejectComment(StringcommentId,Stringreason);voidmarkAsSpam(StringcommentId);ListCommentgetPendingComments();}对外服务基于角色功能进行组合普通用户评论服务packageprinciple.blog.comment.service;importprinciple.blog.comment.service.operations.BasicCommentOperations;importprinciple.blog.comment.service.operations.CommentReplyOperations;/** * 普通用户评论服务 */interfaceRegularUserCommentServiceextendsBasicCommentOperations,CommentReplyOperations{}审核员评论服务packageprinciple.blog.comment.service;importprinciple.blog.comment.service.operations.CommentModerationOperations;importprinciple.blog.comment.service.operations.CommentReplyOperations;/** * 审核员评论服务 */publicinterfaceModeratorCommentServiceextendsCommentService,CommentReplyOperations,CommentModerationOperations{}4.5 依赖倒置原则(Dependency Inversion Principle,DIP)4.5.1定义High-level modules should not depend on low-level modules. Both should depend on absractions. Abstractions should not depend on detailds. Deatils should depend on abstractions.字面意思:高层模块不应该依赖于低层模块两者都应该依赖于抽象。抽象不应该依赖于细节细节应该依赖于抽象。理解抽象比细节更稳定。感觉这是编程思维的一种精华。依赖倒置原则是实现开闭原则的重要手段它的另一种解释也不陌生: 面向接口编程4.5.2 好处高内聚低耦合 以接口维度内聚职责依赖抽象而不是具体可以替换实现降低了耦合扩展性、稳定性: 同开闭原则。4.5.3 违背原则会存在的问题高内聚低耦合 依赖具体的类增加了耦合扩展性、稳定性 同开闭原则。反过来高层依赖底层并且依赖于具体那么当业务变化时需要直接去修改依赖的具体类不易修改而且引入风险极高。4.5.4 如何做面向接口编程。 分析系统的模块及模块间的依赖关系约定对外提供的接口。使用继承时尽量遵循里氏替换原则4.5.5 示例同[[设计模式学习(3) 设计模式原则#4.2.5 示例]]4.6 迪米特法则(Law of DemeterLoD;Principle of Least Knowledge, PLK)4.6.1定义Eache unit should have only limited knowledge about other units: only units ‘closedly’ related to the current unit. Or: Each unit should only talk to its friends; don’t talk to strangers.字面意思:每个单元对于其他单元只应该拥有有限的知识只了解与当前单元关系密切的单元。或者说每个单元只应该与其朋友对话不要与陌生人对话。理解:最少知道原则。一个类对自己依赖的类知道的越少越好只需要知道自己需要的输入输出不用知道实现细节以及被依赖的类的依赖类。被依赖的类应该做好封装仅提供需要使用方知道的功能和接口隔离原则相互补充。接口隔离原则约束了一个类对外应该提供的功能。最小知道原则约束一个类做好这些功能的封装作为使用方时不要依赖陌生类。4.6.2 好处高内聚低耦合 类封装细节只暴露需要提供的方法提高内聚; 仅与依赖的类通信降低耦合稳定性 不依赖陌生的类减少依赖那么一个类的变化能传递的影像随之减小4.6.3 违背原则会存在的问题高内聚低耦合 类内部细节暴露不够内聚依赖增加增加了耦合稳定性 依赖陌生的类当被依赖的类修改时增加了引入风险4.6.4 如何做使用方只依赖需要依赖的类不去关注它的实现细节提供方只暴露该暴露的方法封装细节类关系设计上减少网状依赖。循环依赖更使不得4.6.5 示例还是博客评论业务假如有一个模块依赖评论模块需要依赖CommentService那么它只用和CommetService打交道就可以不用在意内部实现。如果调用Commmet持久层接口那就违背了迪米特法则packageprinciple.blog.test.lod;importprinciple.blog.comment.model.Comment;importprinciple.blog.comment.repository.CommentStorage;importprinciple.blog.comment.service.CommentService;publicclassLodTest{privatefinalCommentServicecommentService;privatefinalCommentStoragecommentStorage;publicLodTest(CommentServicecommentService,CommentStoragecommentStorage){this.commentServicecommentService;this.commentStoragecommentStorage;}voidlod(){// 假设这个类是service层类依赖评论模块的commentServiceCommentcommentnewComment();// 正确示例 仅依赖commentService不和commentStorage交互commentService.postComment(comment.getContent());// 错误示例 不仅和commentService交互也和commentStorage交互commentStorage.save(comment);commentService.postComment(comment.getContent());}}4.7 合成复用原则(Composite Reuse Principle,CRP)4.7.1定义Wherever possible, one shoud prefer composing classes out of componets that already exist, rather than creating new class by inheritance from existing clssses. This principle is known as composition over inheritance.字面意思:在可能的情况下应该优先使用已有的组件来组合类而不是通过继承现有类来创建新类。这一原则被称为组合优于继承。理解:尽量使用组合、聚合的方式来依赖类而不是继承不要为了使用某个类的方法而去继承它符合里氏替换原则不使用继承就不会有继承的弊端问题4.7.2 好处高内聚低耦合 组合、聚合依赖性远远小于继承的方式稳定性 同里氏替换原则4.7.3 违背原则会存在的问题高内聚低耦合 继承的方式依赖性远远大于组合、聚合稳定性 同里氏替换原则4.7.4 如何做尽量使用组合、聚合的方式来构建类的关系而不是使用继承使用继承时尽量遵循里氏替换原则4.7.5 示例还是博客评论业务现在业务发现可以带图进行评论如果采用继承的方式会增加耦合和代码复杂度;如果采用充血模型这里假如校验方法那么图片评论类会重写校验方法违背里氏替换原则增加风险packageprinciple.blog.test.crp.v1;importjava.util.Date;publicclassComment{privateStringid;privateStringauthorId;privateStringarticleId;privateStringparentCommentId;privateStringcontent;privateDatecreatedAt;publicvoidvalidate(){// 校验文本content逻辑}// 省略}继承的方式packageprinciple.blog.test.crp.v1;publicclassImageCommentextendsComment{privateStringurl;privateStringtype;privateStringcaption;Overridepublicvoidvalidate(){super.validate();// 图片校验逻辑}// 省略}使用组合:提取图片信息类组合到原评论类中packageprinciple.blog.test.crp.v2;publicclassImageInfo{privateStringurl;privateStringtype;privateStringcaption;// 省略}packageprinciple.blog.test.crp.v2;importprinciple.blog.comment.validation.CommentFilter;importjava.util.ArrayList;importjava.util.Date;importjava.util.List;publicclassComment{privateStringid;privateStringauthorId;privateStringarticleId;privateStringparentCommentId;privateStringcontent;privateDatecreatedAt;// 组合而不是继承privateListImageInfoimageInfos;publicvoidvalidate(){// 注入所有校验器(包括 文本、图片校验器实现)ListCommentFilterfiltersnewArrayList();// 循环校验}// 省略}