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

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

本文,主要讲解模板模式策略模式状态模式观察者模式访问者模式备忘录模式

1、模板模式

定义:

定义一个操作中的算法的骨架,将一些步骤延迟到子类中。模板模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

优点:1、封装不变部分,扩展可变部分。2、提取公共代码,便于维护。3、行为由父类控制,子类实现。缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,维护成本高。

核心思路:

  • AbstractTemplate:定义一个完整的框架,方法的调用顺序已经确定,但会定义一些抽象的方法留给子类去实现

  • AHandler:具体的业务子类,实现AbstractTemplate中定义的抽象方法,从而形成一个完整的流程逻辑

代码示例:

public TradeFlowActionResult execute(TradeFlowActionParam param, Map context) throws ServiceException {try {    // 业务逻辑校验this.validateBusinessLogic(param, context);} catch (ServiceException ex) {sendGoodsLog.info("SendGoodsAction->validateBusinessLogic got exception. param is " + param, ex);throw ex;} catch (RuntimeException ex) {sendGoodsLog.info("SendGoodsAction->validateBusinessLogic got runtime exception. param is " + param, ex);throw ex;}try {// 卖家发货业务逻辑this.sendGoods(param, context);} catch (ServiceException ex) {sendGoodsLog.info("SendGoodsAction->sendGoods got exception. param is " + param, ex);throw ex;} catch (RuntimeException ex) {sendGoodsLog.info("SendGoodsAction->sendGoods got runtime exception. param is " + param, ex);throw ex;}try {// 补充业务(结果不影响核心业务)this.addition(param, context);} catch (ServiceException ex) {sendGoodsLog.info("SendGoodsAction->addition got exception. param is " + param, ex);throw ex;} catch (RuntimeException ex) {sendGoodsLog.info("SendGoodsAction->addition got runtime exception. param is " + param, ex);throw ex;}// 处理结果return null;
}

上面提到的三个抽象方法(业务逻辑校验、卖家发货业务逻辑、补充业务)都是在子类中实现的。即控制了主流程结构,又不失灵活性,可以让使用者在其基础上定制开发。如果有新的业务玩法进来,原来的流程满足不了需求,我们可以基于模板类编写新的子类。

适用场景:

  • 希望控制算法的主流程,不能随意变更框架,但又想保留子类业务的个性扩展。

  • 去除重复代码。保留父类通用的代码逻辑,让子类不再需要重复处理公用逻辑,只关注特定逻辑,起到去除子类中重复代码的目的。

  • 案例很多,比如 Jenkins 的拉取代码、编译、打包、发布、部署的流程作为一个通用的流程,不同系统(java、python、nodejs等)可以根据自身的需求开发,定制自己的持续发布流程。

2、策略模式

定义:

定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。

由客户端自己决定在什么样的情况下使用哪些具体的策略。

核心思路:

  • 上下文信息类(Context):使用不同的策略环境,根据自身的条件选择不同的策略实现类来完成所需要的操作。他持有一个策略实例的引用。

  • 抽象策略类(Strategy):抽象策略,定义每个策略都要实现的方法

  • 具体策略类(A Realize):负责实现抽象策略中定义的策略方法。

代码示例:

/*** @author 微信公众号:微观技术*/
public interface PromotionStrategy {// 活动类型String promotionType();// 活动优惠int recommand(String productId);
}public class FullSendPromotion implements PromotionStrategy {@Overridepublic String promotionType() {return "FullSend";}@Overridepublic int recommand(String productId) {System.out.println("参加满送活动");return 0;}
}public class FullReducePromotion implements PromotionStrategy {@Overridepublic String promotionType() {return "FullReduce";}@Overridepublic int recommand(String productId) {System.out.println("参加满减活动");return 0;}
}@author 微信公众号:微观技术
public class Context {private static List<PromotionStrategy> promotionStrategyList = new ArrayList<>();static {promotionStrategyList.add(new FullReducePromotion());promotionStrategyList.add(new FullSendPromotion());}public void recommand(String promotionType, String productId) {PromotionStrategy promotionStrategy = null;// 找到对应的策略类for (PromotionStrategy temp : promotionStrategyList) {if (temp.promotionType().equals(promotionType)) {promotionStrategy = temp;}}// 策略子类调用promotionStrategy.recommand(productId);}

首先,定义活动策略的接口类PromotionStrategy,定义接口方法,每一种具体的策略算法都要实现该接口,FullSendPromotionFullReducePromotion

Context负责存储和使用策略,汇集了所有的策略子类。并根据传入的参数,匹配到具体的策略子类,然后调用recommand方法,处理具体的业务。

使用策略的好处:

  • 提升代码的可维护性。不同策略类隔离,互不影响。每一次新增策略时都通过新增类来进行隔离,避免了if-else超大的复杂类

  • 动态快速地替换更多的算法。由于调度策略与算法实现分离,且接口规范固定,我们可以灵活的调整选择不同的策略子类。

适用场景:

  • 压缩文件,提供了 gzip、zip 、rar 等格式,由客户端自己选择哪一种压缩策略。不同策略可以相互替换。

  • 营销活动,根据策略路由选择不同的活动玩法,不同的营销活动隔离,满足开闭原则。

  • 选择权交给了客户端,适合那些经常调整策略的to C 业务,灵活性高。

3、状态模式

定义:

一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。

通过定义一系列状态的变化来控制行为的变化。以电商为例,用户的订单会经历以下这些状态:已下单、已付款、已发货、派送中、待取件、已签收、交易成功、交易关闭等状态。

核心思路:

  • 上下文信息类(OrderContext):存储当前状态的类,对外提供更新状态的方法。

  • 抽象状态类(OrderState):可以是一个接口或抽象类,用于声明状态更新时执行哪些操作

  • 具体状态类(MakeOrderState、PayOrderState、ReceiveGoodOrderState):实现抽象状态类定义的方法,根据具体的场景来指定对应状态改变后的代码实现逻辑。

代码示例:

/*** @author 微信公众号:微观技术* 订单状态,接口定义(扩展实现若干不同状态的子类)*/
public interface OrderState {void handle(OrderContext context);
}// 下单
public class MakeOrderState implements OrderState {public static MakeOrderState instance = new MakeOrderState();@Overridepublic void handle(OrderContext context) {System.out.println("1、创建订单");context.setCurrentOrderState(PayOrderState.instance);}
}// 付款、确认收货,实现类相似,这里省略。。。。// 订单上下文
public class OrderContext {private OrderState currentOrderState;public OrderContext(OrderState currentOrderState) {if (currentOrderState == null) {this.currentOrderState = new MakeOrderState();} else {this.currentOrderState = currentOrderState;}}public void setCurrentOrderState(OrderState currentOrderState) {this.currentOrderState = currentOrderState;}public void execute() {currentOrderState.handle(this);}
}

运行结果:

1、创建订单
2、支付宝付款
3、确认收到货物

状态模式设计的核心点在于找到合适的抽象状态以及状态之间的转移关系,通过改变状态来达到改变行为的目的。

适用场景:

  • 业务需要根据状态的变化,进行不同的操作。比如:电商下单的全流程

  • 不希望有大量的if-else代码堆在一起,希望不同的状态处理逻辑隔离,遵守开闭原则

4、观察者模式

定义:

也称 发布-订阅模式,是一种通知机制,当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。让发送通知的一方(被观察者)和接收通知的一方(观察者,支持多个)能彼此分离,互不影响,该模式在软件开发中非常流行。

像我们常见的KafkaRocketMQ等消息中间件都是采用这种架构模式,还有SpringApplicationEvent 异步事件驱动,有很好的低耦合特性。

类似其他叫法:

  • 发布者 --- 订阅者

  • 生产者 --- 消费者

  • 事件发布 --- 事件监听

核心思路:

  • 发布者(Publisher):定义一系列接口,用来管理和触发订阅者

  • 具体发布者(PublisherImpl):具体实现类,实现Publisher接口定义的方法

  • 订阅者(Observer):观察者接口,当发布者发布消息或事件时,会通知到订阅者进行处理。

  • 具体订阅者(WeixinObserver、EmailObserver):Observer的子类,用来处理具体的业务逻辑

代码示例:

/*** @author 微信公众号:微观技术* <p>* 被观察者*/
public interface Publisher {void add(Observer observer);void remove(Observer observer);void notify(Object object);
}public class PublisherImpl implements Publisher {private List<Observer> observerList = new ArrayList<>();@Overridepublic void add(Observer observer) {observerList.add(observer);}@Overridepublic void remove(Observer observer) {observerList.remove(observer);}@Overridepublic void notify(Object object) {System.out.println("创建订单,并发送通知事件");observerList.stream().forEach(t -> t.handle());}
}// 观察者接口
public interface Observer {public void handle();
}// 观察者子类
public class EmailObserver implements Observer {@Overridepublic void handle() {System.out.println("发送邮件通知!");}
}

观察者模式的核心精髓:被观察者定义了一个通知列表,收集了所有的观察者对象,当被观察者需要发出通知时,就会通知这个列表的所有观察者

适用场景:

  • 当一个对象状态的改变需要改变其他对象时。比如:订单支付成功后,需要通知扣减账户余额

  • 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。比如:微博feed流,粉丝能拉到最新微博

  • 代码的扩展性强,如果需要新增一个观察者业务处理,只需新增一个子类观察者,并注入到被观察者的通知列表即可,代码的耦合性非常低。

5、访问者模式

定义:

访问者模式是一种将操作与对象结构分离的软件设计模式,允许在运行时将一个或多个操作应用于一组对象。

核心思路:

  • 行为接口(RouterVisitor):定义对每个 Element 访问的行为,方法参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的。

  • 行为接口实现类(LinuxRouterVisitor、WindowRouterVisitor):它需要给出对每一个元素类访问时所产生的具体行为。

  • 元素接口(RouterElement):定义一个可以获取访问操作的接口,使客户端对象能够“访问”的入口点。

  • 元素接口实现类(JhjRouterElement、LyqRouterElement):将访问者RouterVisitor传递给此对象作为参数。

代码示例:

/*** @author 微信公众号:微观技术* 定义行为动作*/
public interface RouterVisitor {// 交换机,发送数据void sendData(JhjRouterElement jhjRouterElement);// 路由器,发送数据void sendData(LyqRouterElement lyqRouterElement);
}public class LinuxRouterVisitor implements RouterVisitor {@Overridepublic void sendData(JhjRouterElement jhjRouterElement) {System.out.println("Linux 环境下,交换机发送数据");}@Overridepublic void sendData(LyqRouterElement lyqRouterElement) {System.out.println("Linux 环境下,路由器发送数据");}
}public class WindowRouterVisitor implements RouterVisitor {// 省略。。。
}/*** @author 微信公众号:微观技术* <p>* 路由元素*/
public interface RouterElement {// 发送数据void sendData(RouterVisitor routerVisitor);
}
public class JhjRouterElement implements RouterElement {@Overridepublic void sendData(RouterVisitor routerVisitor) {System.out.println("交换机开始工作。。。");routerVisitor.sendData(this);}
}
public class LyqRouterElement implements RouterElement {// 省略。。。
}

适用场景:

  • 动态绑定不同的对象和对象操作

  • 通过行为与对象结构的分离,实现对象的职责分离,提高代码复用性

6、备忘录模式

定义:

也叫快照模式,用来存储另外一个对象内部状态的快照,便于以后可以恢复。

核心思路:

  • 原始对象(Originator):除了创建自身所需要的属性和业务逻辑外,还通过提供方法 bak()restore(memento) 来保存和恢复对象副本。

  • 备忘录(Memento):用于保存原始对象的所有属性状态,以便在未来进行恢复操作。

代码示例:

/*** @author 微信公众号:微观技术* 原始对象*/
@Data
public class Originator {private Long id;private String productName;private String picture;// 创建快照public Memento bak() {return new Memento(id, productName, picture);}//恢复public void restore(Memento m) {this.id = m.getId();this.productName = m.getProductName();this.picture = m.getPicture();}
}// 快照
@Data
@AllArgsConstructor
public class Memento {private Long id;private String productName;private String picture;
}

适用场景:

  • 在线编辑器,不小心关闭浏览器,重新打开页面,可以从草稿箱恢复之前内容

  • 不希望外界直接访问对象的内部状态,比如:物流包裹

  • 另外像操作系统自动备份,数据库的SAVEPOINT

写在最后

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

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

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

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

看完这篇,code review 谁敢喷你代码写的烂?怼回去!相关推荐

  1. 数学连乘和累加运算符号_看完这篇专栏,别再傻傻地写一大长串的加号和乘号了 #总和与连乘#...

    连乘符号∏ 圆周率π,想必大家小学甚至幼儿园时便耳熟能详了.π的小写我们都清楚是表示圆的周长与直径之比,其值约为3.14,.当我们把π的小腿给捋直,再放大,得到的门似的图形就是π的大写.它是这样的:连 ...

  2. 看完这篇文章之后,终于明白了编译到底怎么回事。

    看完这篇文章之后,终于明白了编译到底怎么回事. 1 对于同一个语句,有如下三种:高级语言.低级语言.机器语言的表示 C语言  a=b+1; 汇编语言  mov -0xc(%ebp),%eax add ...

  3. 看完这篇文章之后,终于明白了编译到底怎么回事

    看完这篇文章之后,终于明白了编译到底怎么回事. 1 对于同一个语句,有如下三种:高级语言.低级语言.机器语言的表示 C语言  a=b+1; 汇编语言  mov -0xc(%ebp),%eax add ...

  4. JVM难学?那是因为你没认真看完这篇文章

    JVM难学?那是因为你没认真看完这篇文章 一:虚拟机内存图解 JAVA程序运行与虚拟机之上,运行时需要内存空间.虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理. 虚拟机管理 ...

  5. 为何看完这篇RxHttp Http请求框架会觉得如此销魂,全文干货建议收藏!

    前言 RxHttp相较于retrofit,功能上,两者均能实现,并无多大差异,更多的差异体现功能的使用上,也就是易用性,如对文件上传/下载/进度监听的操作上,RxHttp用及简的API,可以说碾压re ...

  6. 程序员要怎么高效学习Java,大学生or小白的你看完这篇的你离BAT又近了一大步

    这篇文章大体上会从以下几个部分展开: 认清自己. 学习目的. 时间管理. 学习方法. 学习的步骤. 获取知识的途径 影响学习的几个因素 自己的心态. 外物的影响. 其他想说的 大学生的学习 一些感悟 ...

  7. 禁止查看写好的宏_【收藏】亚马逊Listing不知道怎么写??看完此篇,即刻破单!...

    [收藏]亚马逊Listing不知道怎么写??看完此篇,即刻破单! 众所周知,在亚马逊平台上大部分交易来自于排位靠前的listing决定的,可见listing的重要性.这里有不少新卖家在选完品之后,不知 ...

  8. 看完这篇再也不怕 Redis 面试了

    看完这篇再也不怕 Redis 面试了 0x00.前言 Redis是跨语言的共同技术点,无论是Java还是C++都会问到,所以是个高频面试点. 笔者是2017年才开始接触Redis的,期间自己搭过单机版 ...

  9. 我就不信看完这篇你还搞不懂信息熵

    我就不信看完这篇你还搞不懂信息熵 https://mp.weixin.qq.com/s/7NrB0UtmELXD3UNO3C6jGA 让我们说人话!好的数学概念都应该是通俗易懂的. 信息熵,信息熵,怎 ...

最新文章

  1. 树莓派安装docker
  2. c语言实验七实验报告,C语言实验七 数 实验报告.doc
  3. entity、model和domain三者区别
  4. 字符流写数据的5种方式
  5. 国内首款 5G 机型开售;Google Chrome 大部分插件无人用;Firefox 69 Beta 9 发布 | 极客头条...
  6. RiskSense Spotlight:全球知名开源软件漏洞分析报告
  7. C语言编程入门——程序练习(下)
  8. 剑指Offer_16_合并两个排序的链表
  9. 直播内容抢先看|基于 AUTOSAR 技术的 SOA 软件平台实践
  10. 网络安全策略防御加固
  11. 6572 Phone call分析
  12. 访问者模式Visitor
  13. CSS3解析抖音 LOGO制作
  14. AI遮天传 ML-SVM
  15. ff14怎么显示服务器,ff14如何选择服务器
  16. 听说大数据工资很高,是不是很难学?
  17. 大佬谈 996, 马云,刘强东,李国庆,人民日报
  18. DSP28335下程序提示低功耗模式
  19. Arduino Uno 实验8——HC-SR04 超声波测距模块
  20. PSIM仿真之:Buck转换器输入阻抗仿真

热门文章

  1. 我是怎么提高单片机编程能力的?
  2. unix oracle控制台,Linux平台下启动oracle11gEM控制台
  3. 是什么轮胎_为什么现在的车轮胎轮毂尺寸越来越大
  4. 李超线段树(Li-Chao Segment Tree)
  5. 【学习笔记】和式(《具体数学》第二章)
  6. 【网络流24题】解题报告:C、最小路径覆盖问题(有向无环图最小路径覆盖)(最大流)
  7. php如何批量导入题库,批量文本导入试题
  8. mysql通用分页_MySQL海量数据的通用存储过程分页代码
  9. POJ-3687-Labeling Balls
  10. 手把手教你React(一)JSX与虚拟DOM