共计 14167 个字符,预计需要花费 36 分钟才能阅读完成。
平时咱们写代码呢,少数状况都是 流水线式 写代码,根本就能够实现业务逻辑了。如何在写代码中找到乐趣呢 ,我感觉,最好的形式就是: 应用设计模式优化本人的业务代码。明天跟大家聊聊日常工作中,我都应用过哪些设计模式。
1. 策略模式
1.1 业务场景
假如有这样的业务场景,大数据系统把文件推送过去,依据不同类型采取 不同的解析 形式。少数的小伙伴就会写出以下的代码:
if(type=="A"){// 依照 A 格局解析}else if(type=="B"){// 按 B 格局解析}else{// 依照默认格局解析}
这个代码可能会存在哪些 问题呢?
- 如果分支变多,这里的代码就会变得 臃肿,难以保护,可读性低。
- 如果你须要接入一种新的解析类型,那只能在 原有代码上批改。
说得业余一点的话,就是以上代码,违反了面向对象编程的 开闭准则 以及 繁多准则。
- 开闭准则(对于扩大是凋谢的,然而对于批改是关闭的):减少或者删除某个逻辑,都须要批改到原来代码
- 繁多准则(规定一个类应该只有一个发生变化的起因):批改任何类型的分支逻辑代码,都须要改变以后类的代码。
如果你的代码就是酱紫:有多个 if…else 等条件分支,并且每个条件分支,能够封装起来替换的,咱们就能够应用 策略模式 来优化。
1.2 策略模式定义
策略模式 定义了算法族,别离封装起来,让它们之间能够互相替换,此模式让算法的变动独立于应用算法的的客户。这个策略模式的定义是不是有点形象呢?那咱们来看点通俗易懂的比喻:
假如你跟不同性情类型的小姐姐约会,要用不同的策略,有的请电影比拟好,有的则去吃小吃成果不错,有的去逛街买买买最合适。当然,目标都是为了失去小姐姐的芳心,请看电影、吃小吃、逛街就是不同的策略。
策略模式针对一组算法,将每一个算法封装到具备独特接口的独立的类中,从而使得它们能够互相替换。
1.3 策略模式应用
策略模式怎么应用呢?酱紫实现的:
- 一个接口或者抽象类,外面两个办法(一个办法匹配类型,一个可替换的逻辑实现办法)
- 不同策略的差异化实现(就是说,不同策略的实现类)
- 应用策略模式
1.3.1 一个接口,两个办法
public interface IFileStrategy {
// 属于哪种文件解析类型
FileTypeResolveEnum gainFileType();
// 封装的专用算法(具体的解析办法)void resolve(Object objectparam);
}
1.3.2 不同策略的差异化实现
A 类型策略具体实现
@Component
public class AFileResolve implements IFileStrategy {
@Override
public FileTypeResolveEnum gainFileType() {return FileTypeResolveEnum.File_A_RESOLVE;}
@Override
public void resolve(Object objectparam) {logger.info("A 类型解析文件,参数:{}",objectparam);
// A 类型解析具体逻辑
}
}
B 类型策略具体实现
@Component
public class BFileResolve implements IFileStrategy {
@Override
public FileTypeResolveEnum gainFileType() {return FileTypeResolveEnum.File_B_RESOLVE;}
@Override
public void resolve(Object objectparam) {logger.info("B 类型解析文件,参数:{}",objectparam);
// B 类型解析具体逻辑
}
}
默认类型策略具体实现
@Component
public class DefaultFileResolve implements IFileStrategy {
@Override
public FileTypeResolveEnum gainFileType() {return FileTypeResolveEnum.File_DEFAULT_RESOLVE;}
@Override
public void resolve(Object objectparam) {logger.info("默认类型解析文件,参数:{}",objectparam);
// 默认类型解析具体逻辑
}
}
1.3.3 应用策略模式
如何应用呢?咱们借助 spring 的生命周期,应用 ApplicationContextAware 接口,把对用的策略,初始化到 map 外面。而后对外提供 resolveFile 办法即可。
/**
* @author
*/
@Component
public class StrategyUseService implements ApplicationContextAware{private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
public void resolveFile(FileTypeResolveEnum fileTypeResolveEnum, Object objectParam) {IFileStrategy iFileStrategy = iFileStrategyMap.get(fileTypeResolveEnum);
if (iFileStrategy != null) {iFileStrategy.resolve(objectParam);
}
}
// 把不同策略放到 map
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
}
}
2. 责任链模式
2.1 业务场景
咱们来看一个常见的业务场景,下订单。下订单接口,根本的逻辑,个别有参数非空校验、平安校验、黑名单校验、规定拦挡等等。很多搭档会应用异样来实现:
public class Order {public void checkNullParam(Object param){
// 参数非空校验
throw new RuntimeException();}
public void checkSecurity(){
// 平安校验
throw new RuntimeException();}
public void checkBackList(){
// 黑名单校验
throw new RuntimeException();}
public void checkRule(){
// 规定拦挡
throw new RuntimeException();}
public static void main(String[] args) {Order order= new Order();
try{order.checkNullParam();
order.checkSecurity ();
order.checkBackList();
order2.checkRule();
System.out.println("order success");
}catch (RuntimeException e){System.out.println("order fail");
}
}
}
这段代码应用了 异样 来做逻辑条件判断,如果后续逻辑越来越简单的话,会呈现一些问题:如异样只能返回异样信息,不能返回更多的字段,这时候须要 自定义异样类。
并且,阿里开发手册规定:禁止用异样做逻辑判断。
【强制】异样不要用来做流程管制,条件管制。阐明:异样设计的初衷是解决程序运行中的各种意外状况,且异样的解决效率比条件判断形式要低很多。
如何优化这段代码呢?能够思考 责任链模式
2.2 责任链模式定义
当你想要让一个 以上的对象 有机会可能解决某个申请的时候,就应用 责任链模式。
责任链模式为申请创立了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)解决申请事务,如果某个对象节点解决完了,就能够依据理论业务需要传递给下一个节点持续解决或者返回处理完毕。这种模式给予申请的类型,对申请的发送者和接收者进行解耦。
责任链模式实际上是一种解决申请的模式,它让多个处理器(对象节点)都有机会解决该申请,直到其中某个解决胜利为止。责任链模式把多个处理器串成链,而后让申请在链上传递:
打个比喻:
假如你早晨去上选修课,为了能够走点走,坐到了最初一排。来到教室,发现后面坐了好几个丑陋的小姐姐,于是你找张纸条,写上:“你好, 能够做我的女朋友吗?如果不违心请向前传”。纸条就一个接一个地传上去了,起初传到第一排的那个妹子手上,她把纸条交给老师,据说老师 40 多岁未婚 …
2.3 责任链模式应用
责任链模式怎么应用呢?
- 一个接口或者抽象类
- 每个对象差异化解决
- 对象链(数组)初始化(连起来)
2.3.1 一个接口或者抽象类
这个接口或者抽象类,须要:
- 有一个指向责任下一个对象的属性
- 一个设置下一个对象的 set 办法
- 给子类对象差异化实现的办法(如以下代码的 doFilter 办法)
/**
* 关注公众号:捡田螺的小男孩
*/
public abstract class AbstractHandler {
// 责任链中的下一个对象
private AbstractHandler nextHandler;
/**
* 责任链的下一个对象
*/
public void setNextHandler(AbstractHandler nextHandler){this.nextHandler = nextHandler;}
/**
* 具体参数拦挡逻辑, 给子类去实现
*/
public void filter(Request request, Response response) {doFilter(request, response);
if (getNextHandler() != null) {getNextHandler().filter(request, response);
}
}
public AbstractHandler getNextHandler() {return nextHandler;}
abstract void doFilter(Request filterRequest, Response response);
}
2.3.2 每个对象差异化解决
责任链上,每个对象的 差异化 解决,如本大节的业务场景,就有参数校验对象、平安校验对象、黑名单校验对象、规定拦挡对象
/**
* 参数校验对象
**/
@Component
@Order(1) // 程序排第 1,最先校验
public class CheckParamFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {System.out.println("非空参数查看");
}
}
/**
* 平安校验对象
*/
@Component
@Order(2) // 校验程序排第 2
public class CheckSecurityFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//invoke Security check
System.out.println("平安调用校验");
}
}
/**
* 黑名单校验对象
*/
@Component
@Order(3) // 校验程序排第 3
public class CheckBlackFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//invoke black list check
System.out.println("校验黑名单");
}
}
/**
* 规定拦挡对象
*/
@Component
@Order(4) // 校验程序排第 4
public class CheckRuleFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//check rule
System.out.println("check rule");
}
}
2.3.3 对象链连起来(初始化)&& 应用
@Component("ChainPatternDemo")
public class ChainPatternDemo {
// 主动注入各个责任链的对象
@Autowired
private List<AbstractHandler> abstractHandleList;
private AbstractHandler abstractHandler;
//spring 注入后主动执行,责任链的对象连接起来
@PostConstruct
public void initializeChainFilter(){for(int i = 0;i<abstractHandleList.size();i++){if(i == 0){abstractHandler = abstractHandleList.get(0);
}else{AbstractHandler currentHander = abstractHandleList.get(i - 1);
AbstractHandler nextHander = abstractHandleList.get(i);
currentHander.setNextHandler(nextHander);
}
}
}
// 间接调用这个办法应用
public Response exec(Request request, Response response) {abstractHandler.filter(request, response);
return response;
}
public AbstractHandler getAbstractHandler() {return abstractHandler;}
public void setAbstractHandler(AbstractHandler abstractHandler) {this.abstractHandler = abstractHandler;}
}
运行后果如下:
非空参数查看
平安调用校验
校验黑名单
check rule
3. 模板办法模式
3.1 业务场景
假如咱们有这么一个业务场景:外部零碎不同商户,调用咱们零碎接口,去跟内部第三方零碎交互(http 形式)。走相似这么一个流程,如下:
一个申请都会经验这几个流程:
- 查问商户信息
- 对申请报文加签
- 发送 http 申请进来
- 对返回的报文验签
这里,有的商户可能是走代理进来的,有的是走直连。假如以后有 A,B 商户接入,不少搭档可能这么实现,伪代码如下:
// 商户 A 解决句柄
CompanyAHandler implements RequestHandler {Resp hander(req){
// 查问商户信息
queryMerchantInfo();
// 加签
signature();
//http 申请(A 商户假如走的是代理)httpRequestbyProxy()
// 验签
verify();}
}
// 商户 B 解决句柄
CompanyBHandler implements RequestHandler {Resp hander(Rreq){
// 查问商户信息
queryMerchantInfo();
// 加签
signature();
// http 申请(B 商户不走代理,直连)httpRequestbyDirect();
// 验签
verify();}
}
假如新加一个 C 商户接入,你须要再实现一套这样的代码。显然,这样代码就 反复 了,一些通用的办法,却在每一个子类都从新写了这一办法。
如何优化呢?能够应用 模板办法模式。
3.2 模板办法模式定义
定义一个操作中的算法的骨架流程,而将一些步骤提早到子类中,使得子类能够不扭转一个算法的构造即可重定义该算法的某些特定步骤。它的核心思想就是:定义一个操作的一系列步骤,对于某些临时确定不下来的步骤,就留给子类去实现,这样不同的子类就能够定义出不同的步骤。
打个艰深的比喻:
模式举例:追女朋友要先“牵手”,再“拥抱”,再“接吻”,再“拍拍.. 额.. 手”。至于具体你用左手还是右手牵,无所谓,然而整个过程,定了一个流程模板,依照模板来就行。
3.3 模板办法应用
- 一个抽象类,定义骨架流程(形象办法放一起)
- 确定的独特办法步骤,放到抽象类(去除形象办法标记)
- 不确定的步骤,给子类去差异化实现
咱们持续那以上的举例的业务流程例子,来一起用 模板办法优化一下哈:
3.3.1 一个抽象类,定义骨架流程
因为一个个申请通过的流程为一下步骤:
- 查问商户信息
- 对申请报文加签
- 发送 http 申请进来
- 对返回的报文验签
所以咱们就能够定义一个抽象类,蕴含申请流程的几个办法,办法首先都定义为形象办法哈:
/**
* 抽象类定义骨架流程(查问商户信息,加签,http 申请,验签)*/
abstract class AbstractMerchantService {
// 查问商户信息
abstract queryMerchantInfo();
// 加签
abstract signature();
//http 申请
abstract httpRequest();
// 验签
abstract verifySinature();}
3.3.2 确定的独特办法步骤,放到抽象类
abstract class AbstractMerchantService {
// 模板办法流程
Resp handlerTempPlate(req){
// 查问商户信息
queryMerchantInfo();
// 加签
signature();
//http 申请
httpRequest();
// 验签
verifySinature();}
// Http 是否走代理(提供给子类实现)abstract boolean isRequestByProxy();}
3.3.3 不确定的步骤,给子类去差异化实现
因为是否走代理流程是 不确定 的,所以给子类去实现。
商户 A 的申请实现:
CompanyAServiceImpl extends AbstractMerchantService{Resp hander(req){return handlerTempPlate(req);
}
// 走 http 代理的
boolean isRequestByProxy(){return true;}
商户 B 的申请实现:
CompanyBServiceImpl extends AbstractMerchantService{Resp hander(req){return handlerTempPlate(req);
}
// 公司 B 是不走代理的
boolean isRequestByProxy(){return false;}
4. 观察者模式
4.1 业务场景
登陆注册应该是最常见的业务场景了。就拿 注册 来说事,咱们常常会遇到相似的场景,就是用户注册胜利后,咱们给用户发一条音讯,又或者发个邮件等等,因而常常有如下的代码:
void register(User user){
insertRegisterUser(user);
sendIMMessage();
sendEmail();}
这块代码会有什么问题呢?如果产品又加需要:当初注册胜利的用户,再给用户发一条短信告诉。于是你又得改 register 办法的代码了。。。这是不是违反了 开闭准则 啦。
void register(User user){
insertRegisterUser(user);
sendIMMessage();
sendMobileMessage();
sendEmail();}
并且,如果调 发短信的接口失败 了,是不是又影响到用户注册了?!这时候,是不是得加个异步办法给 告诉音讯 才好。。。
实际上,咱们能够应用观察者模式优化。
4.2 观察者模式定义
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态产生扭转时,所有依赖于它的对象都失去告诉并被实现业务的更新。
观察者模式属于行为模式,一个对象(被观察者)的状态产生扭转,所有的依赖对象(观察者对象)都将失去告诉,进行播送告诉。它的次要成员就是 观察者和被观察者。
- 被观察者(Observerable):指标对象,状态发生变化时,将告诉所有的观察者。
- 观察者(observer):承受被观察者的状态变动告诉,执行事后定义的业务。
应用场景: 实现某件事情后,异步告诉场景。如,登陆胜利,发个 IM 音讯等等。
4.3 观察者模式应用
观察者模式实现的话,还是比较简单的。
- 一个被观察者的类 Observerable ;
- 多个观察者 Observer;
- 观察者的差异化实现
- 经典观察者模式封装:EventBus 实战
4.3.1 一个被观察者的类 Observerable 和 多个观察者 Observer
public class Observerable {
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState() {return state;}
public void setState(int state) {notifyAllObservers();
}
// 增加观察者
public void addServer(Observer observer){observers.add(observer);
}
// 移除观察者
public void removeServer(Observer observer){observers.remove(observer);
}
// 告诉
public void notifyAllObservers(int state){if(state!=1){System.out.println(“不是告诉的状态”);
return ;
}
for (Observer observer : observers) {observer.doEvent();
}
}
}
4.3.2 观察者的差异化实现
// 观察者
interface Observer {void doEvent();
}
//Im 音讯
IMMessageObserver implements Observer{
void doEvent(){System.out.println("发送 IM 音讯");
}
}
// 手机短信
MobileNoObserver implements Observer{
void doEvent(){System.out.println("发送短信音讯");
}
}
//EmailNo
EmailObserver implements Observer{
void doEvent(){System.out.println("发送 email 音讯");
}
}
4.3.3 EventBus 实战
本人搞一套观察者模式的代码,还是有点小麻烦。实际上,Guava EventBus 就封装好了,它 提供一套基于注解的事件总线,api 能够灵便的应用,爽歪歪。
咱们来看下 EventBus 的实战代码哈,首先能够申明一个 EventBusCenter 类,它相似于以上被观察者那种角色 Observerable。
public class EventBusCenter {private static EventBus eventBus = new EventBus();
private EventBusCenter() {}
public static EventBus getInstance() {return eventBus;}
// 增加观察者
public static void register(Object obj) {eventBus.register(obj);
}
// 移除观察者
public static void unregister(Object obj) {eventBus.unregister(obj);
}
// 把音讯推给观察者
public static void post(Object obj) {eventBus.post(obj);
}
}
而后再申明观察者 EventListener
public class EventListener {
@Subscribe // 加了订阅,这里标记这个办法是事件处理办法
public void handle(NotifyEvent notifyEvent) {System.out.println("发送 IM 音讯" + notifyEvent.getImNo());
System.out.println("发送短信音讯" + notifyEvent.getMobileNo());
System.out.println("发送 Email 音讯" + notifyEvent.getEmailNo());
}
}
// 告诉事件类
public class NotifyEvent {
private String mobileNo;
private String emailNo;
private String imNo;
public NotifyEvent(String mobileNo, String emailNo, String imNo) {
this.mobileNo = mobileNo;
this.emailNo = emailNo;
this.imNo = imNo;
}
}
应用 demo 测试:
public class EventBusDemoTest {public static void main(String[] args) {EventListener eventListener = new EventListener();
EventBusCenter.register(eventListener);
EventBusCenter.post(new NotifyEvent("13372817283", "123@qq.com", "666"));
}
}
运行后果:
发送 IM 音讯 666
发送短信音讯 13372817283
发送 Email 音讯 123@qq.com
5. 工厂模式
5.1 业务场景
工厂模式个别配合策略模式一起应用。用来去优化大量的 if…else… 或 switch…case… 条件语句。
咱们就取第一大节中策略模式那个例子吧。依据不同的文件解析类型,创立不同的解析对象
IFileStrategy getFileStrategy(FileTypeResolveEnum fileType){
IFileStrategy fileStrategy ;
if(fileType=FileTypeResolveEnum.File_A_RESOLVE){fileStrategy = new AFileResolve();
}else if(fileType=FileTypeResolveEnum.File_A_RESOLV){fileStrategy = new BFileResolve();
}else{fileStrategy = new DefaultFileResolve();
}
return fileStrategy;
}
其实这就是 工厂模式,定义一个创建对象的接口,让其子类本人决定实例化哪一个工厂类,工厂模式使其创立过程提早到子类进行。
策略模式的例子,没有应用上一段代码,而是借助 spring 的个性,搞了一个工厂模式,哈哈,小伙伴们能够回去那个例子细品一下,我把代码再搬下来,小伙伴们再品一下吧:
/**
* @author
*/
@Component
public class StrategyUseService implements ApplicationContextAware{private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
// 把所有的文件类型解析的对象,放到 map,须要应用时,信手拈来即可。这就是工厂模式的一种体现啦
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
}
}
5.2 应用工厂模式
定义工厂模式也是比较简单的:
- 一个工厂接口,提供一个创立不同对象的办法。
- 其子类实现工厂接口,结构不同对象
- 应用工厂模式
5.3.1 一个工厂接口
interface IFileResolveFactory{void resolve();
}
5.3.2 不同子类实现工厂接口
class AFileResolve implements IFileResolveFactory{void resolve(){System.out.println("文件 A 类型解析");
}
}
class BFileResolve implements IFileResolveFactory{void resolve(){System.out.println("文件 B 类型解析");
}
}
class DefaultFileResolve implements IFileResolveFactory{void resolve(){System.out.println("默认文件类型解析");
}
}
5.3.3 应用工厂模式
// 结构不同的工厂对象
IFileResolveFactory fileResolveFactory;
if(fileType=“A”){fileResolveFactory = new AFileResolve();
}else if(fileType=“B”){fileResolveFactory = new BFileResolve();
}else{fileResolveFactory = new DefaultFileResolve();
}
fileResolveFactory.resolve();
个别状况下,对于工厂模式,你不会看到以上的代码。工厂模式会跟配合其余设计模式如策略模式一起呈现的。
6. 单例模式
6.1 业务场景
单例模式,保障一个类仅有一个实例,并提供一个拜访它的全局拜访点。I/ O 与数据库的连贯, 个别就用单例模式实现 de 的。Windows 外面的 Task Manager(工作管理器)也是很典型的单例模式。
来看一个单例模式的例子
/**
*
*/
public class LanHanSingleton {
private static LanHanSingleton instance;
private LanHanSingleton(){}
public static LanHanSingleton getInstance(){if (instance == null) {instance = new LanHanSingleton();
}
return instance;
}
}
以上的例子,就是 懒汉式 的单例实现。实例在须要用到的时候,才去创立,就比拟懒。如果有则返回,没有则新建,须要加下 synchronized 关键字,要不然可能存在 线性平安问题。
6.2 单例模式的经典写法
其实单例模式还有有好几种实现形式,如饿汉模式,双重校验锁,动态外部类,枚举等实现形式。
6.2.1 饿汉模式
public class EHanSingleton {private static EHanSingleton instance = new EHanSingleton();
private EHanSingleton(){}
public static EHanSingleton getInstance() {return instance;}
}
饿汉模式,它 比拟饥饿、比拟怠惰,实例在初始化的时候就曾经建好了,不论你前面有没有用到,都先新建好实例再说。这个就没有线程平安的问题,然而呢,节约内存空间呀。
6.2.2 双重校验锁
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){if (instance == null) {synchronized (DoubleCheckSingleton.class) {if (instance == null) {instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
双重校验锁实现的单例模式,综合了懒汉式和饿汉式两者的优缺点。以上代码例子中,在 synchronized 关键字内外都加了一层 if 条件判断,这样既保证了线程平安,又比间接上锁进步了执行效率,还节俭了内存空间。
6.2.3 动态外部类
public class InnerClassSingleton {
private static class InnerClassSingletonHolder{private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static final InnerClassSingleton getInstance(){return InnerClassSingletonHolder.INSTANCE;}
}
动态外部类的实现形式,成果有点相似双重校验锁。但这种形式只实用于动态域场景,双重校验锁形式可在实例域须要提早初始化时应用。
6.2.4 枚举
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance(){return INSTANCE;}
}
枚举实现的单例,代码简洁清晰。并且它还主动反对序列化机制,相对避免屡次实例化。