面对复杂的业务场景,千变万化的客户需求,如何以一变应万变,以最小的开发成本快速落地实现,同时保证系统有着较低的复杂度,能够保证系统后续de持续迭代能力,让系统拥有较高的可扩展性。

这些是一个合格的架构师必须修炼的基础内功,但是如何修炼这门神功???

我将常用的软件设计模式,做了汇总,目录如下:

(考虑到内容篇幅较大,为了便于大家阅读,将软件设计模式系列(共23个)拆分成四篇文章,每篇文章讲解六个设计模式,采用不同的颜色区分,便于快速消化记忆)

本文是主要讲解桥接模式组合模式装饰模式门面模式代理模式责任链模式

1、桥接模式

自然界一般由实体和行为组成。当然为了提升系统的扩展性,它们两个又可以各自抽象,然后在抽象类中描述两者的依赖。

定义:

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

什么场景使用桥接模式?

  • 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。

  • 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

核心思路:

  • 抽象实体:定义的一种抽象分类。比如:人

  • 具体实体:继承抽象实体的子类实体。比如:中国人、美国人、韩国人

  • 抽象行为:定义抽象实体中具备的多种行为。比如:学汉语、吃汉堡

  • 具体行为:实现抽象行为的具体算法。比如:中国人学汉语、美国人吃汉堡

代码示例:

/*** @author 微信公众号:微观技术* 抽象实体*/
public abstract class AbstractEntity {protected AbstractBehavior abstractBehavior;public AbstractEntity(AbstractBehavior abstractBehavior) {this.abstractBehavior = abstractBehavior;}public abstract void out();}/*** 抽象行为*/
public interface AbstractBehavior {public String action(String name);
}/*** 关于食物的行为*/
public class FoodBehavior implements AbstractBehavior {@Overridepublic String action(String name) {if ("中国人".equals(name)) {return "吃 饺子";} else if ("美国人".equals(name)) {return "吃 汉堡";}return null;}
}

桥接模式是将抽象与抽象之间分离,具体实现类依赖于抽象。抽象的分离间接完成了具体类与具体类之间的解耦,它们之间使用抽象来进行组合或聚合,而不再靠多重继承来实现。本质是将一个对象的实体和行为分离,然后再基于这两个维度进行独立的演化。

适用场景:

  • 拆分复杂的类对象时。当一个类中包含大量对象和方法时,既不方便阅读,也不方便修改。

  • 希望从多个独立维度上扩展时。比如,系统功能性和非功能性角度,业务或技术角度等。

  • 运行时,组合不同的组件

2、组合模式

定义:

组合模式也称整体模式,把一组相似的对象当作一个单一的对象,然后将对象组合成树形结构以表示整个层次结构。

这里边有两个关键点:1、树形结构分层  2、业务统一化来简化操作

核心思路:

  • 抽象组件(AbstractNode):定义需要实现的统一操作。

  • 组合节点(CompositeNode):抽象组件的衍生子类,包含了若干孩子节点(其它组合节点或叶子节点)。

  • 叶子节点(LeafNode):抽象组件的子类,但它的下面没有子节点。

代码示例:

public abstract class AbstractNode {public abstract void add(AbstractNode abstractNode);public abstract void remove(AbstractNode abstractNode);public abstract void action();
}public class CompositeNode extends AbstractNode {private Long nodeId;private List<AbstractNode> childNodes;  //存放子节点列表public CompositeNode(Long nodeId, List<AbstractNode> childNodes) {this.nodeId = nodeId;this.childNodes = childNodes;}@Overridepublic void add(AbstractNode abstractNode) {childNodes.add(abstractNode);}@Overridepublic void remove(AbstractNode abstractNode) {childNodes.remove(abstractNode);}@Overridepublic void action() {for (AbstractNode childNode : childNodes) {childNode.action();}}
}public class LeafNode extends AbstractNode {private Long nodeId;public LeafNode(Long nodeId) {this.nodeId = nodeId;}@Overridepublic void add(AbstractNode abstractNode) {// 无子节点,无需处理return;}@Overridepublic void remove(AbstractNode abstractNode) {// 无子节点,无需处理return;}@Overridepublic void action() {System.out.println("叶子节点编号:" + nodeId);}
}

叶子节点不能新增、删除子节点,所以对应的方法为空。

组合模式本质上封装了复杂结构的内在变化,让使用者通过一个统一的整体来使用对象之间的结构。数据结构方面支持树形结构环形结构网状结构。如我们常见的 深度优先搜索广度优先搜索都是采用这种模式。

适用场景:

  • 一组对象按照某种层级结构进行管理。如:管理文件夹和文件,管理订单下的商品。

  • 需要按照统一的行为来处理复杂结构中的对象

  • 快速扩展对象组合。

手机开始是按品牌来归属分类,现在业务增加价格维度分类,我们只需要引入新的分支节点,按新的维度构建组合关系。

3、装饰模式

定义:

动态地向一个现有对象添加新的职责和行为,同时又不改变其结构,相当于对现有的对象进行包装。

核心思路:

  • 抽象组件(Component):装饰器基类,定义组件的基本功能

  • 具体组件(ConcreteComponent):抽象组件的具体实现

  • 抽象装饰器(Decorator):包含抽象组件的引用

  • 具体装饰器(ConcreteDecorator):抽象装饰器的子类,并重写组件接口方法,同时可以添加附加功能。

代码示例:

public abstract class Component {public abstract void execute();
}public class ConcreteComponent extends Component {@Overridepublic void execute() {System.out.println("具体子类 ConcreteComponent invoke !");}
}public class Decorator extends Component {protected Component component;public Decorator(Component component) {this.component = component;}@Overridepublic void execute() {component.execute();}
}public class ConcreteDecorator extends Decorator {public ConcreteDecorator(Component component) {super(component);}@Overridepublic void execute() {System.out.println("装饰器子类 ConcreteDecorator invoke !");super.execute();}
}

装饰模式本质上就是给已有不可修改的类附加新的功能,同时还能很方便地撤销。

适用场景:

  • 无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的功能

  • 将业务逻辑组织为层次结构,可以为各层创建一个装饰,在运行时将各种不同逻辑组合成对象。由于这些对象都遵循通用接口,客户端代码能以相同的方式使用这些对象。

  • 不支持继承扩展类的场景。如:final 关键字限制了某个类的进一步扩展,可以通过装饰器对其进行封装,从而具备扩展能力。

4、门面模式

定义:

门面模式提供一个高层次的接口,要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,使得子系统更易于使用。

门面模式要求我们使用统一的标准与系统交互,比如:我们打印日志基本会选择slf4j框架,其内部统一了log4jlog4j2CommonLog等日志框架,简化了我们的开发成本。

核心思路:

  • 门面系统。接收外部请求,并将请求转发给适当的子系统进行处理

  • 子系统。表示某个领域内的功能实现、或者具体子接口实现,比如,订单、支付等,专门处理由门面系统指派的任务。

简单来讲,引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口。

可能很多人有疑问,这个不就是代理模式吗?

门面模式可能代理的是多个接口,而代理模式通常只是代理一个接口。

业务场景:

移动互联网,我们都习惯了在线支付,相信很多人在付款时都听过这么一句话,”微信支付还是支付宝“,商户根据用户反馈再针对性选择收款渠道。

是不是很繁琐,为了解决这个问题,市面就有了聚合支付(该领域做非常棒的是收钱吧),整个业务模式就是这节要讲的门面模式,不管你用什么软件支付,只要打开付款二维码即可,收钱吧底层识别解析二维码,并根据扫描结果自动适配对应的收款渠道,完成用户的扣款动作,确实带来不错的用户体验。

优点:

  • 简化复杂系统,提供统一接口规范。比如:JPA提供了统一Java持久层API,底层适配多样化的存储系统。

  • 复杂的业务逻辑由内部子系统消化,只要对外接口规范不变,外部调用方不需要频繁修改

  • 扩展性较好,类似于SPI架构一样,支持水平扩展。

  • 较高的平滑过渡性。比如:我们要对老的系统架构升级,开发一系列新接口来替换原来的老接口,过渡期需要新老灰度测试、流量切换、平滑升级,可以采用该模式。门面模式在兼容多套系统、系统重构方面是把利器。

5、代理模式

定义:

为其他对象提供一种代理以控制对这个对象的访问

现实场景:

  • 房产中介

  • 包工头

核心思路:

  • 抽象主题类(AbstractSubject):定义接口方法,供客户端使用

  • 主题实现类(RealSubject):实现了抽象主题类的接口方法

  • 代理类(Proxy):实现了抽象主题类的接口方法,内部包含主题实现类的逻辑, 同时还包含一些自身的扩展操作。

代理模式与适配器模式相似。但适配器模式是转换为新的接口,而代理模式不会改变原有接口。

代码示例:

/*** @author 微信公众号:微观技术*/
public interface AbstractSubject {void execute();
}public class RealSubject implements AbstractSubject {@Overridepublic void execute() {System.out.println("我是Tom哥,我要努力工作!");}
}public class Proxy implements AbstractSubject {private AbstractSubject abstractSubject;public Proxy(AbstractSubject abstractSubject) {this.abstractSubject = abstractSubject;}@Overridepublic void execute() {System.out.println("老板给Tom哥分配工作了。。。");abstractSubject.execute();}
}

按使用职责分为静态代理和动态代理。

  • 静态代理,代理类需要自己编写代码完成。

  • 动态代理,代理类通过 Proxy#newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法生成。

  • JDK实现的代理中不管是静态代理还是动态代理,都是面向接口编程。CGLib可以不限制一定是接口。

优点:

  • 职责清晰

  • 高扩展,只要实现了接口,都可以用代理

  • 智能化,动态代理

  • 降低了对象的直接耦合

适用场景:

  • 远程代理。无法直接操作远程对象。比如:Dubbo、gRPC,提供远程服务,客户端调用时需要走参数组装、序列化、网络传输等操作,这些通用逻辑都可以封装到代理中,客户端调用代理对象访问远程服务,就像调用本地对象一样方便。

  • 保护代理。当客户端通过代理对象访问原始对象时,代理对象会根据规则判断客户端是否有权限访问。比如:防火墙

  • 日志代理。比如:日志监控,正常业务访问时,调用代理,增加一些额外的日志记录功能。

  • 虚拟代理,适用于延迟初始化,用小对象表示大对象的场景,减少资源损耗,提升运行速度。

  • 不希望改变原对象,但需要增加类似于权限控制、日志、流控等附加功能时,可以使用代理模式。

6、责任链模式

定义:

责任链模式是一种行为设计模式,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链。收到请求后,每个处理者均可对请求进行处理,或将其传递给链中的下个处理者。

责任链模式是对数据结构中的链表结构的具体应用。

核心思路:

  • 抽象处理者(Handler):定义一个接口,内部包含处理方法和下一个节点的引用对象

  • 具体处理者(ConcreteHandler):抽象处理者的实现子类,判断本次请求是否处理,如果需要则处理,否则跳过,然后将请求转发给下一个节点。

优点:

  • 降低了对象之间的耦合度。链上各个节点各司其职,通过上下文传递数据,避免直接依赖。

  • 增强系统的可扩展性。如果有新的业务需求,只需要在合适的位置增加一个链节点即可,满足开闭原则。

  • 灵活性强。如果业务有变化,需要对工作流程做调整,只需要动态调整链上节点的次序即可。甚至为了满足多元化业务的多样化需求,我们可以为不同的业务类型定义自己的专属执行顺序。

  • 简化了对象之间的连接。每个对象只需保存下一个节点的引用,而不需保持所有节点。

  • 责任明确。每个节点只需处理自己的工作,如果不处理则传递给下一个对象。明确各类的责任范围,符合类的单一职责原则。

像我们常见的网关架构推荐使用该模式,通过服务编排,可以自由地在任意位置添加或移除节点,满足一系列个性化功能。

写在最后

设计模式很多人都学习过,但项目实战时总是晕晕乎乎,原因在于没有了解其核心是什么,底层逻辑是什么,《设计模式:可复用面向对象的基础》有讲过,

在设计中思考什么应该变化,并封装会发生变化的概念。

软件架构的精髓:找到变化,封装变化。

业务千变万化,没有固定的编码答案,千万不要硬套设计模式。无论选择哪一种设计模式,尽量要能满足SOLID原则,自我review是否满足业务的持续扩展性。有句话说的好,“不论白猫黑猫,能抓老鼠就是好猫。”

代码写的烂,经常被同事怼,教你一招!相关推荐

  1. 遗留代码写的烂,我能怎么办?

    作者 | 王晔倞,责编 | 郭芮 头图 | CSDN 下载自视觉中国 上周四,我团队里的某技术经理在一次代码评审会上跟一位开发同学发生了争吵,而事情的起因是某段看似合理,却存在明显性能问题的代码片段. ...

  2. 看完这篇,code review 谁敢喷你代码写的烂?怼回去!

    我将常用的软件设计模式,做了汇总,目录如下: (考虑到内容篇幅较大,为了便于大家阅读,将软件设计模式系列(共23个)拆分成四篇文章,每篇文章讲解六个设计模式,采用不同的颜色区分,便于快速消化记忆) 本 ...

  3. 代码写的太乱?一分钟教你优化

    简介 我们在利用pandas开展数据分析时,应尽量避免过于「碎片化」的组织代码,尤其是创建出过多不必要的「中间变量」,既浪费了「内存」,又带来了关于变量命名的麻烦,更不利于整体分析过程代码的可读性,因 ...

  4. 作为一个程序员,敲代码还是二指禅??? 教你几招,让你打字速度飞起

    同一个人在不同的打字环境下,速度是不同的.打字环境包括三种:看打.想打.听打.所以,在追求打字速度的时候,要先确认自己是在什么样的打字环境下打字.因为这会决定什么样的输入法适合你.工欲善其事,必先利其 ...

  5. 新入职百度某员工发飙:前人代码写得像一坨屎,不能忍受,颠覆了对大厂的认知,...

    热文导读|   点击标题阅读 互联网寒冬下,程序员如何突围提升自己? Android进阶之仿抖音的音乐旋转效果 裸辞两个月,海投一个月,从 Android 转战 Web 前端的求职之路 今天在浏览某职 ...

  6. 如何才能写出一手逼疯同事的烂代码?

    要是想写个烂代码,我们只需遵守这十九条准则? 「代码写得好」是对机器学习研究者及开发者最好的赞扬.其第一层意思是说,你的模型非常好,有自己的理解与修正:第二层意思是说代码的结构.命名规则.编写逻辑都非 ...

  7. 《转》十种更好的表达“你的代码写的很烂”的方法

    如果你有一个同事,他写的程序与其说是代码,不如说更像希腊神话中女妖美杜莎的头发,你当然不能熟视无睹,你应该做出一些反应,但你可选的合适的反应方式并没有多少:自己默默的帮他整理清楚.向上级抱怨.向其他同 ...

  8. 当程序员说“这代码写的可真烂”,他们的意思是“这烂代码不是我写的”。而当他们说这段代码有些“小问题”时,很可能这代码是他们自己写的...

    英文原文:What Programmers Say vs. What They Mean 你是否听到过同事说"这段代码不言自明"?你的同事的这句话的实际意思是这段代码不需要写注释. ...

  9. 手把手教你写出几十种让同事无法维护的代码!

    对,你没看错 本文就是教你怎么写出让同事无法维护的代码! 01 程序命名 容易输入的变量名.比如:Fred,asdf 单字母的变量名.比如:a,b,c, x,y,z(如果不够用,可以考虑a1,a2,a ...

最新文章

  1. zbb20170613 linux 安装 mysql
  2. Python PIL.Image和OpenCV图像格式相互转换
  3. 一个Lex/Yacc完整的示例(可使用C++)
  4. pc端游戏修改器_原神:不要吐槽手机内存了,想要获得最佳游戏体验,PC端最合适...
  5. 返回值 包装类_(九)Java常用类
  6. Mysql (21)---连接的使用
  7. mysql中值换行显示为乱码_MySQL数据移植中的乱码问题
  8. libstdc和glibc的一些共享库问题
  9. python中seaborn库_GitHub - a13544835729/python-seaborn: python seaborn库基础用法
  10. 机器学习,深度学习的资料和工具库大全
  11. [后缀数组]JZOJ 3337 wyl889的TLE
  12. linux下安装xamp
  13. [USACO 2009 Dec G]Video Game Troubles-----分组背包的改良版
  14. Java中带有T Z格式(UTC是世界标准时间)的时间转换为date,string,long类型
  15. Markdown | 最具有效率的写作工具一次性掌握
  16. 解决:服务器种挖矿程序的一次实战记录~
  17. 三体 给岁月以文明, 给时光以生命
  18. RALL机制的线程锁
  19. Codeforces 332B Maximum Absurdity(DP+前缀和处理)
  20. 童年依恋风格影响成年后的两性关系

热门文章

  1. html实现点赞评论功能_html的canvas实现画布功能
  2. jq 修改swal的标题_js-jquery-SweetAlert【一】使用
  3. 阔力梯的树(2020 CCPC Wannafly Winter Camp Day2 Div.12 )dsu on tree
  4. Matlab入门笔记
  5. hangfire 过期记录_时隔数月后我又有减肥的想法(饮食日记录)
  6. 解题报告:luoguP1462 通往奥格瑞玛的道路(二分、最短路)
  7. A - 棋盘问题 POJ - 1321(dfs)
  8. 数据库系统概念 第六版 大学数据库代码
  9. P1603 斯诺登的密码
  10. 用matlab画曲顶柱体费用数据,数值积分的matlab实现