作者:Tom哥
公众号:微观技术
博客:https://offercome.cn
人生理念:知道的越多,不知道的越多,努力去学
面对复杂的业务场景,千变万化的客户需求,如何以一变应万变,以最小的开发成本快速落地实现,同时保证系统有着较低的复杂度,能够保证系统后续de持续迭代能力,让系统拥有较高的可扩展性。
这些是一个合格的架构师必须修炼的基础内功,但是如何修炼这门神功???
我将常用的软件设计模式,做了汇总,目录如下:
(考虑到内容篇幅较大,为了便于大家阅读,将软件设计模式系列(共23个)拆分成四篇文章,每篇文章讲解六个设计模式,采用不同的颜色区分,便于快速消化记忆)
前文回顾:
本文是第三期,主要讲解模板模式
、策略模式
、状态模式
、观察者模式
、访问者模式
、备忘录模式
定义:
定义一个操作中的算法的骨架,将一些步骤延迟到子类中。模板模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,维护成本高。
核心思路:
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;
}
上面提到的三个抽象方法(业务逻辑校验、卖家发货业务逻辑、补充业务)都是在子类中实现的。 即控制了主流程结构,又不失灵活性,可以让使用者在其基础上定制开发。如果有新的业务玩法进来,原来的流程满足不了需求,我们可以基于模板类编写新的子类。
适用场景:
定义:
定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。
由客户端自己决定在什么样的情况下使用哪些具体的策略。
核心思路:
代码示例:
/**
* @author 微信公众号:微观技术
*/
public interface PromotionStrategy {
// 活动类型
String promotionType();
// 活动优惠
int recommand(String productId);
}
public class FullSendPromotion implements PromotionStrategy {
@Override
public String promotionType() {
return "FullSend";
}
@Override
public int recommand(String productId) {
System.out.println("参加满送活动");
return 0;
}
}
public class FullReducePromotion implements PromotionStrategy {
@Override
public String promotionType() {
return "FullReduce";
}
@Override
public 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
,定义接口方法,每一种具体的策略算法都要实现该接口,FullSendPromotion
和FullReducePromotion
。
Context
负责存储和使用策略,汇集了所有的策略子类。并根据传入的参数,匹配到具体的策略子类,然后调用recommand
方法,处理具体的业务。
使用策略的好处:
适用场景:
定义:
一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。
通过定义一系列状态的变化来控制行为的变化。以电商为例,用户的订单会经历以下这些状态:已下单、已付款、已发货、派送中、待取件、已签收、交易成功、交易关闭等状态。
核心思路:
当前状态
的类,对外提供更新状态的方法。代码示例:
/**
* @author 微信公众号:微观技术
* 订单状态,接口定义(扩展实现若干不同状态的子类)
*/
public interface OrderState {
void handle(OrderContext context);
}
// 下单
public class MakeOrderState implements OrderState {
public static MakeOrderState instance = new MakeOrderState();
@Override
public 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
代码堆在一起,希望不同的状态处理逻辑隔离,遵守开闭原则定义:
也称 发布-订阅模式,是一种通知机制,当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。让发送通知的一方(被观察者)和接收通知的一方(观察者,支持多个)能彼此分离,互不影响,该模式在软件开发中非常流行。
像我们常见的Kafka
、RocketMQ
等消息中间件都是采用这种架构模式,还有Spring
的 ApplicationEvent
异步事件驱动,有很好的低耦合特性。
类似其他叫法:
核心思路:
Publisher
接口定义的方法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<>();
@Override
public void add(Observer observer) {
observerList.add(observer);
}
@Override
public void remove(Observer observer) {
observerList.remove(observer);
}
@Override
public void notify(Object object) {
System.out.println("创建订单,并发送通知事件");
observerList.stream().forEach(t -> t.handle());
}
}
// 观察者接口
public interface Observer {
public void handle();
}
// 观察者子类
public class EmailObserver implements Observer {
@Override
public void handle() {
System.out.println("发送邮件通知!");
}
}
观察者模式的核心精髓:被观察者定义了一个通知列表,收集了所有的观察者对象,当被观察者
需要发出通知时,就会通知这个列表的所有观察者
。
适用场景:
观察者
业务处理,只需新增一个子类观察者
,并注入到被观察者
的通知列表即可,代码的耦合性非常低。定义:
访问者模式是一种将
操作
与对象结构
分离的软件设计模式,允许在运行时将一个或多个操作应用于一组对象。
核心思路:
RouterVisitor
传递给此对象作为参数。代码示例:
/**
* @author 微信公众号:微观技术
* 定义行为动作
*/
public interface RouterVisitor {
// 交换机,发送数据
void sendData(JhjRouterElement jhjRouterElement);
// 路由器,发送数据
void sendData(LyqRouterElement lyqRouterElement);
}
public class LinuxRouterVisitor implements RouterVisitor {
@Override
public void sendData(JhjRouterElement jhjRouterElement) {
System.out.println("Linux 环境下,交换机发送数据");
}
@Override
public 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 {
@Override
public void sendData(RouterVisitor routerVisitor) {
System.out.println("交换机开始工作。。。");
routerVisitor.sendData(this);
}
}
public class LyqRouterElement implements RouterElement {
// 省略。。。
}
适用场景:
定义:
也叫快照模式,用来存储另外一个对象内部状态的快照,便于以后可以恢复。
核心思路:
bak()
和 restore(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是否满足业务的持续扩展性。有句话说的好,“不论白猫黑猫,能抓老鼠就是好猫。”