平时咱们写代码呢,少数状况都是流水线式写代码,根本就能够实现业务逻辑了。如何在写代码中找到乐趣呢,我感觉,最好的形式就是:应用设计模式优化本人的业务代码。明天跟大家聊聊日常工作中,我都应用过哪些设计模式。

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 类型策略具体实现

@Componentpublic 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 类型策略具体实现

@Componentpublic class BFileResolve implements IFileStrategy {    @Override    public FileTypeResolveEnum gainFileType() {        return FileTypeResolveEnum.File_B_RESOLVE;    }    @Override    public void resolve(Object objectparam) {      logger.info("B 类型解析文件,参数:{}",objectparam);      //B类型解析具体逻辑    }}

默认类型策略具体实现

@Componentpublic 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  */@Componentpublic 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) //校验程序排第2public class CheckSecurityFilterObject extends AbstractHandler {    @Override    public void doFilter(Request request, Response response) {        //invoke Security check        System.out.println("平安调用校验");    }}/** *  黑名单校验对象 */@Component@Order(3) //校验程序排第3public class CheckBlackFilterObject extends AbstractHandler {    @Override    public void doFilter(Request request, Response response) {        //invoke black list check        System.out.println("校验黑名单");    }}/** *  规定拦挡对象 */@Component@Order(4) //校验程序排第4public 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("发送短信音讯");    }}//EmailNoEmailObserver 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  */@Componentpublic 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;    }}

枚举实现的单例,代码简洁清晰。并且它还主动反对序列化机制,相对避免屡次实例化。