
▐ 模式定义

▐ 适用场景
适用于多节点的流程处理,每一个节点完成各自负责的部分,节点之间不知道彼此的存在,好比 OA 的审批流,Java Web 开发中的 Filter 机制。php
多个对象能够处理同一个请求,但具体由哪一个对象处理则在运行时动态决定。java
在请求处理者不明确的状况下向对个对象中的一个提交一个请求。web
须要动态处理一组对象处理请求。算法
举一个生活中的例子,好比你忽然想世界那么大你想去看看,可是处于现实的你还不能丢了工做,获得请假的OA申请,请假天数若是是半天到1天,可能直接主管批准便可;若是是1到3天的假期,须要部门经理批准;若是是3天到30天,则须要总经理审批;大于30天,正常不会批准。这种简单的流程便可试用于咱们当前业务场景。编程
▐ 实践经验
业务流程很简单:设计模式
打电话注销信用卡微信
工做人员注销信用卡app
注销信用卡有个背景是这样的,若是信用卡存在帐单未还清,存在溢出款,存在特殊年费未使用等状况是不容许注销信用卡的,鉴于此,咱们在注销以前加了一套是否容许注销的检验逻辑。编辑器
大致以下:ide
是否存在帐单未还清,好比有已出帐单未还清,有未出帐单未还清,有年费管理费等未还清等。
是否存在溢出款多余的钱。
是否存在高额积分未使用,需用户确认放弃积分等。
针对这几类状况创建了三类过滤器,分别是:
UserLogoutUnpaidBillsLimitFilter:是否存在未还清金额。
UserLogoutOverflowLimitFilter:是否存在溢出款。
UserLogoutGiveUpPointsLimitFilter:是否放弃高额金额。
判断逻辑是先经过UserLogoutUnpaidBillsLimitFilter判断当前用户是否能够注销信用卡。若是容许继续由 UserLogoutOverflowLimitFilter 判断是否存在溢出款,是否能够注销信用卡;若是没有溢出款继续由UserLogoutGiveUpPointsLimitFilter 判断当前用户是否存在高额积分,前面三条判断,只要有一个不知足就提早返回。
public boolean canLogout(String userId) { //获取用户信息 UserInfo userInfo = getUserInfo(userId);
// 构造注销信用卡限制过滤器链条 LogoutLimitFilterChain filterChain = new LogoutLimitFilterChain(); filterChain.addFilter(new UserLogoutUnpaidBillsLimitFilter()); filterChain.addFilter(new UserLogoutOverflowLimitFilter()); filterChain.addFilter(new UserLogoutGiveUpPointsLimitFilter()); boolean checkResult = filterChain.doFilter(filterChain, userInfo);
//filterChain.doFilter方法 public boolean doFilter (LogoutLimitFilterChain filterChain, UserInfo userInfo){ //迭代调用过滤器 if (index < filters.size()) { return filters.get(index++).doFilter(filterChain, userInfo); } } }
//UserLogoutUnpaidBillsLimitFilter.doFilter方法 public boolean doFilter(LogoutLimitFilterChain filterChain, UserInfo userInfo) { //获取用户当前欠款金额 UserCardBillInfo userCardBillInfo = findUserCardBillInfo(userInfo);
// 判断当前卡用户是否容许消费 if (userCardBillInfo != null) { if ((!CAN_LOGOUT.equals(userCardBillInfo.getEnabledLogout()))) { return false; } } //其他状况,继续日后传递 return filterChain.doFilter(filterChain, memberInfo, consumeConfig); }
//UserLogoutOverflowLimitFilter.doFilter方法 public boolean doFilter(LogoutLimitFilterChain filterChain, UserInfo userInfo) { //判断用户是否存在溢出款 UserCardDeposit userCardDeposit = findUserCardDeposit(userInfo);
// 判断当前卡用户是否容许消费 if (userCardDeposit != null) { if (userCardDeposit.getDeposit() != 0) { return false; } } //其他状况,继续日后传递 return filterChain.doFilter(filterChain, memberInfo, consumeConfig); }
总结:将每种限制条件的判断逻辑封装到了具体的 Filter 中,若是某种限制条件的逻辑有修改不会影响其余条件,若是须要新加限制条件只须要从新构造一个 Filter 织入到 FilterChain 上便可。
责任链中一个处理者对象,其中只有两个行为,一是处理请求,二是将请求转送给下一个节点,不容许某个处理者对象在处理了请求后又将请求转送给上一个节点的状况。对于一条责任链来讲,一个请求最终只有两种状况,一是被某个处理对象所处理,另外一个是全部对象均未对其处理,前一种状况称该责任链为纯的责任链,对于后一种状况称为不纯的责任链,实际应用中,多为不纯的责任链。
策略设计模式
▐ 模式定义
策略这个词应该怎么理解,打个比方说,咱们出门的时候会选择不一样的出行方式,好比骑自行车、坐公交、坐火车、坐飞机等等,这些出行方式,每一种都是一个策略。
再好比咱们去逛商场,商场如今正在搞活动,有打折的、有满减的、有返利的等等,其实无论商场如何进行促销,说到底都是一些算法,这些算法自己只是一种策略,而且这些算法是随时均可能互相替换的,好比针对同一件商品,今天打八折、明天满100减30,这些策略间是能够互换的。
策略模式(Strategy Pattern)是定义了一组算法,将每一个算法都封装起来,而且使它们之间能够互换。
▐ 适用场景
主要是为了消除大量的 if else 代码,将每种判断背后的算法逻辑提取到具体的策略对象中,当算法逻辑修改时对使用者无感知,只须要修改策略对象内部逻辑便可。这类策略对象通常都实现了某个共同的接口,能够达到互换的目的。
多个类只有算法或行为上稍有不一样的场景
算法须要自由切换的场景
须要屏蔽算法规则的场景
▐ 实践经验
业务流程很简单:
挑选商品
选择不一样的优惠方式结帐
好比即将到来的双十一活动某些线下商家举办活动,折扣力度以下满300-80,部分商品5折,根据不一样会员等级享受不一样的折扣最低7折,周年庆活动可享8折等等。假如这些活动折扣不可同享,那么如何去实现以及考虑可扩展性的话策略模式是一种不错的选择。
/** * 抽象折扣策略接口 */
public abstract class DiscountStrategy { /** * 计算折扣后的价格 * @param price 原价 * @return 折扣后的价格 */ public abstract CalculationResult getDiscountPrice(Long userId ,BigDecimal price);}
/** * 满减活动 -- 满300减80 */public class FullReductionStrategyOne extends DiscountStrategy { /** * 计算折扣后的价格 * @param price 原价 * @return */ @Override public CalculationResult getDiscountPrice(Long userId ,BigDecimal price) { if (price.doubleValue() < 300) { return price; } BigDecimal dealPrice= price.subtract(BigDecimal.valueOf(80)); return getCalculationResult(userId,dealPrice); }}
/** * 部分商品5折 */public class MerchandiseDiscountStrategy extends DiscountStrategy { /** * 计算折扣后的价格 * @param price 原价 * @return */ @Override public CalculationResult getDiscountPrice(BigDecimal price) { BigDecimal dealPrice= price.multiply(BigDecimal.valueOf(0.5)); return getCalculationResult(userId,dealPrice); }}
/***当有新的需求式,咱们只须要添加一个新的接口便可,不须要修改原有的具体策略实现代码便可完成。*定义完策略后,咱们再定义一个”环境角色”,假设咱们这个环境角色就使用价格对象吧*/
public class Price {
private DiscountStrategy discountStrategy;
/** * 定义一个无参构造,用于实例对象 */ private Price(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; }
/** * 获取折扣后的价格 * * @param price 原始价格 * @return */ public CalculationResult discount(Long userId,BigDecimal price) { return discountStrategy.getDiscountPrice(userId ,price); }}
模板设计模式
▐ 模式定义
模板的价值就在于骨架的定义,骨架内部将问题处理的流程已经定义好,通用的处理逻辑通常由父类实现,个性化的处理逻辑由子类实现。
好比炒土豆丝和炒麻婆豆腐,大致逻辑都是:
一、切菜
二、放油
三、炒菜
四、出锅
1,2,4 都差很少,可是第 3 步是不同的,炒土豆丝得拿铲子翻炒,可是炒麻婆豆腐得拿勺子轻推,不然豆腐会烂。
▐ 使用场景
不一样场景的处理流程,部分逻辑是通用的,能够放到父类中做为通用实现,部分逻辑是个性化的,须要子类去个性实现。
模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类能够按须要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
▐ 实践经验
仍是接着以前商品折扣的例子来讲,后期咱们新加了两个需求:
用户享受不一样折扣增长 trace。
用户享受折扣后是否升级会员等级。
因此如今的流程变成了这样:
一、trace 开始。
二、计算用户不一样折扣力度。
三、是否容许升级会员等级,若是容许执行升级会员等级逻辑。
四、trace 结束。
其中 1 和 4 是通用的,2 和 3 是个性化的,鉴于此能够在折扣策略以前增长了一层父类的策略,将通用逻辑放到了父类中。
修改后的代码以下:
abstract class AbstractDiscountStrategy implements DiscountStrategy{
public CalculationResult getDiscountPrice(Long userId ,BigDecimal price) { //1.构造span Span span = buildSpan(); //2.具体通道推送逻辑由子类实现 CalculationResult calculationResult =getCalculationResult(userId,price); //3.是否容许升级会员等级,若是容许执行升级逻辑 if(!calculationResult.isSuccess() && canUpgrade()){ upgradeLevel(userId,calculationResult); }
//4.trace结束 span.finish(); return calculationResult; }
//具体推送逻辑由子类实现 protected abstract CalculationResult getCalculationResult(Long userId,BigDecimal price) ;
//是否容许升级会员等级由子类实现 protected abstract boolean canUpgrade(Long userId,CallResult callResult);
}
/** * 满减活动 -- 满300减80 */public class FullReductionStrategyOne extends AbstractDiscountStrategy { protectedCalculationResult getCalculationResult(Long userId,BigDecimal price){ //执行折扣逻辑 }
protected boolean canUpgrade(Long userId,CallResult callResult){ return false }}
观察者设计模式
▐ 模式定义
拍卖的时候,拍卖师观察最高标价,而后通知给其余竞价者竞价 这种模式就可使用观察者模式。顾名思义,此模式须要有观察者(Observer)和被观察者(Observable)两类角色。当 Observable 状态变化时会通知 Observer,Observer 通常会实现一类通用的接口。
好比 java.util.Observer,Observable 须要通知 Observer 时,逐个调用 Observer 的 update 方法便可,Observer 的处理成功与否不该该影响 Observable 的流程。
▐ 使用场景
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。好比,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
一个对象(Observable)状态改变须要通知其余对象,Observer 的存在不影响 Observable 的处理结果,Observer 的增删对 Observable 无感知。
好比 Kafka 的消息订阅,Producer 发送一条消息到 Topic,至因而 1 个仍是 10 个 Consumer 订阅这个 Topic,Producer 是不须要关注的。
▐ 实践经验
在责任链设计模式那块我经过三个 Filter 解决了注销信用卡限制检验的问题,其中有一个 Filter 是用来检验用户积分的,我这里只是读取用户的积分总额和次数,那么消费次数得到积分的累加是怎么完成的呢?
其实累加这块就用到了观察者模式,具体来说是这样,当交易系统收到支付成功回调时会经过 Spring 的事件机制发布“支付成功事件”。
这样负责积分消费次数累加和负责语音播报的订阅者就会收到“支付成功事件”,进而作各自的业务逻辑。
画个简单的图描述一下:
/**支付回调处理者*/PayCallBackController implements ApplicationContextAware { private ApplicationContext applicationContext;
//若是想获取applicationContext须要实现ApplicationContextAware接口,Spring容器会回调setApplicationContext方法将applicationContext注入进来 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
"/pay/callback.do") (value = public View callback(HttpServletRequest request){ if(paySuccess(request){ //构造支付成功事件 PaySuccessEvent event = buildPaySuccessEvent(...); //经过applicationContext发布事件,从而达到通知观察者的目的 this.applicationContext.publishEvent(event); } }}/** * 语音播报处理者 * */public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent>{ public void onApplicationEvent(PaySuccessEvent event) { //语音播报逻辑 }}
//其余处理者的逻辑相似
总结:观察者模式将被观察者和观察者之间作了解耦,观察者存在与否不会影响被观察者的现有逻辑。
装饰器设计模式
▐ 模式定义
装饰器模式(Decorator Pattern)容许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是做为现有的类的一个包装。这种模式建立了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
装饰器用来包装原有的类,在对使用者透明的状况下作功能的加强,好比 Java 中的 BufferedInputStream 能够对其包装的 InputStream 作加强,从而提供缓冲功能。
▐ 使用场景
在不影响其余对象的状况下,以动态、透明的方式给单个对象添加职责。须要动态地给一个对象增长功能,这些功能也能够动态地被撤销。 当不能采用继承的方式对系统进行扩展或者继承。但愿对原有类的功能作加强,但又不但愿增长过多子类时,可使用装饰器模式来达到一样的效果。
▐ 实践经验
有一个咖啡店,销售各类各样的咖啡,拿铁,卡布奇洛,蓝山咖啡等,在冲泡前,会询问顾客是否要加糖,加奶,加薄荷等。这样不一样的咖啡配上不一样的调料就会卖出不一样的价格。
/*** 抽象类Coffee*/public abstract class Coffee { /** * 获取咖啡得名字 */ public abstract String getName();
/** * 获取咖啡的价格 */ public abstract double getPrice();}
/***利用继承和组合的结合,如今咱们能够考虑设计出一个装饰类,它也继承自coffee,*而且它内部有一个coffee的实例对象*/public abstract class CoffeeDecorator implements Coffee { private Coffee delegate;
public CoffeeDecorator(Coffee coffee) { this.delegate = coffee; }
@Override public String getName() { return delegate.getName(); }
@Override public double getPrice() { return delegate.getPrice(); }}
/***牛奶咖啡的装饰者模式的案例*/public class MilkCoffeeDecorator extends CoffeeDecorator { public MilkCoffeeDecorator(Coffee coffee) { super(coffee); }
@Override public String getName() { return "牛奶, " + super.getName(); }
@Override public double getPrice() { return 1.1 + super.getPrice(); }}
//其余咖啡的模式相似
/***测试案例 能够经过加入不用内容 咖啡名称和价格都是不一样的*/public class App { public static void main(String[] args) { // 获得一杯原始的蓝山咖啡 Coffee blueCoffee = new BlueCoffee(); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 加入牛奶 blueCoffee = new MilkCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入薄荷 blueCoffee = new MintCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入糖 blueCoffee = new SugarCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); }}
总结:使用装饰器模式作了功能的加强,对使用者来讲只须要作简单的组合就能继续使用原功能。装饰器模式充分展现了组合的灵活。利用它来实现扩展。它同时也是开闭原则的体现。若是相对某个类实现运行时功能动态的扩展。这个时候你就能够考虑使用装饰者模式!
桥接设计模式
▐ 模式定义
桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。
桥接(Bridge)是用于把抽象化与实现化解耦,使得两者能够独立变化。这种类型的设计模式属于结构型模式,它经过提供抽象化和实现化之间的桥接结构,来实现两者的解耦。
▐ 使用场景
若是一个系统须要在抽象类和具体类之间增长更多的灵活性,避免在两个层次之间创建静态的继承关系,经过桥接模式可使它们在抽象层创建一个关联关系。
抽象部分和实现部分能够以继承的方式独立扩展而互不影响,在程序运行时能够动态的将一个抽象类子类的对象和一个实现类子类的对象进行组合,及系统须要对抽象类角色和实现类角色进行动态耦合。
一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都须要独立进行扩展。
对于那些不但愿使用继承或由于多层继承致使系统的个数急剧增长的系统,桥接模式尤其适用。
▐ 实践经验
性能管理系统中,数据产生后须要通过采集,汇聚,入库三个流程,用户才能查询使用。采集能够是snmp采集,也能够是ems采集;汇聚可使storm汇聚,也能够是spark汇聚;入库能够是hdfs入库,也能够是mppdb入库。针对不一样场景,咱们能够灵活选择不一样的采集,汇聚,入库方式。这种一个功能须要多种服务支持,每种服务又有不一样类型的实现,使用桥接模式再适合不过。
桥接模式,顾名思义,就是把每种服务看作一座桥,咱们能够根据实际场景选择不一样的桥。上述例子表示数据产生到可使用以前须要通过三座桥:采集桥->汇聚桥->入库桥。每座桥能够选择不一样的构造。
/** * * 采集桥采集服务 * */public abstract class CollectionService { abstract void execute(); public void run(){ execute(); }}
/** * 汇聚桥 汇聚服务 * */public abstract class AggregationService { public void run(){ if(null != collectionService) { collectionService.run(); } execute(); } abstract void execute(); CollectionService collectionService; public AggregationService(CollectionService collectionService){ this.collectionService = collectionService; }}
/** * * 入库桥 入库服务 * */public abstract class StoreService { public void run(){ if(null != aggregationService) { aggregationService.run(); } execute(); } abstract void execute(); AggregationService aggregationService; public StoreService(AggregationService aggregationService){ this.aggregationService = aggregationService; }}
/**** EMS采集桥**/
public class EMSCollectionService extends CollectionService{ void execute() { System.out.println("EMS collection."); }}
/**** SNMP采集桥**/public class SNMPCollectionService extends CollectionService{ void execute() { System.out.println("SNMP collection."); }}
/**** Storm汇聚桥**/public class StormAggregationService extends AggregationService{ public StormAggregationService(CollectionService collectionService) { super(collectionService); }
void execute() { System.out.println("Storm aggregation."); }}
/**** Spark汇聚桥**/public class SparkAggregationService extends AggregationService{ public SparkAggregationService(CollectionService collectionService) { super(collectionService); }
void execute() { System.out.println("Spark aggregation."); }}
/**** MPPDB汇聚桥**/public class MPPDBStoreService extends StoreService{ public MPPDBStoreService(AggregationService aggregationService){ super(aggregationService); }
void execute() { System.out.println("MPPDB store."); }}
/**** HDFS汇聚桥**/public class HDFSStoreService extends StoreService{ public HDFSStoreService(AggregationService aggregationService) { super(aggregationService); }
void execute() { System.out.println("HDFS store."); }}
/** * * 类功能说明: 桥接模式测试 */public class BridgeTest { public static void main(String[] args){ CollectionService snmpService = new SNMPCollectionService(); AggregationService stormService = new StormAggregationService(snmpService); StoreService hdfsService = new HDFSStoreService(stormService); hdfsService.run(); }}
总结:桥接模式能够将系统中稳定的部分和可扩展的部分解耦,使得系统更加容易扩展,且知足OCP原则,对调用者修改关闭。
✿ 拓展阅读




本文分享自微信公众号 - 淘系技术(AlibabaMTT)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。