23

设计模式 | 4分钟搞懂10种设计模式

 3 years ago
source link: https://segmentfault.com/a/1190000023730985
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

虽然你觉得大炮肯定是个标题党,但你终究还是点进来了(别打我,手动狗头保命),毕竟这性价比也太高了,4分钟10种,如果是真的就赚大发了。

3IJfyyF.jpg!mobile

但是大炮可以肯定的告诉你,只要正儿八经的参与过几个项目,有个一两年的开发经验。你肯定已经用过并且可能已经理解了一些设计模式了,只是并不自知而已。今天大炮就来给你好好缕一缕。

36va2eI.jpg!mobile

单例、工厂模式

这两个设计模式大家都不陌生,很多读者也都已经自学过了。比如饿汉、懒汉单例模式,简单、抽象工厂模式。而且这两个模式,就像兄弟一样,经常结对出现。所以大炮放到一起来讲,我们先来看一行代码:

private static final Logger logger = LoggerFactory.getLogger(UserService.class);

非常眼熟吧,这是我们平时用来获取日志对象的一段代码,但是重点并不是这个 logger 而是 LoggerFactory,顾名思义用来 专门用来造 logger 的 工厂 ,是一个工厂模式很好的体现。同时作为工厂呢,它又是单例的,毕竟new一个工厂就够了嘛,自己实例化那么多个干嘛呢, 单例杜绝了反复创建和销毁对象的开销

同时它又用到了门面模式,这行代码是不是很牛,用到了三个设计模式,我们接着往下看。

门面模式

在《阿里巴巴Java开发手册》中,关于日志章节中专门有提到:

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
private static final Logger logger = LoggerFactory.getLogger(Abc.class);

有点感觉没,门面模式其实就是把自己作为一个接口,自己并不提供真正的实现。什么,还是没到位?那我们看看下面这段代码:

zIBN3e.png!mobile

是不是又很眼熟了,下单要干挺多事情,保存订单,保存订单项,减库存...或者还有其他业务,如果所有这些方法的实现都放到 createOrder 这个方法里面,那代码肯定是又长又臭,洋洋洒洒就奔着几百上千行去了。所以有经验的你们肯定会抽到好几个子方法里面,就像上面一样,然后挨个调用下就行了。(PS,真正的下单代码肯定不是这么玩的,高并发下肯定挂了,看个意思就好)

没错,这也是门面模式,createOrder 方法并没有给出具体的实现,直接调用了几个子方法而已。可能有些读者心里已经发出了嘲讽: 就这?就这? 这也算设计模式?我自己都会写啦。

biEZz2I.png!mobile

没错,就这~ 设计模式就是对经验的总结,然后提出了一些建议和规范。 其实并不是多么高深的东西。现在想想,如果给优秀的我们早生几年,有些设计模式的创造者还指不定是谁呢。回到上面的代码,虽然没多少技术含量,但对调用者而言,我 管你怎么实现,我轻轻调一下 createOrder 方法就完事 了。这正是门面模式的精髓所在。

嗯,一行代码 get 到了三个设计模式。咱们继续。

代理模式

代理模式,是一个非常重要的设计模式,不理解它可能会让你直接无缘大厂。大家一定要学会并理解这个东西。我们先来看一段代码:

zIBN3e.png!mobile

How old are you ? 怎么老是你 ? 没错,还是上面的图。这次的关注点大家放在 @Transaction 上。注解本身没啥用,只是作为一个标记,有时候会携带一些配置信息。老开发已经知道我要讲啥了,这里用到了Spring 中一个非常的重要特性:AOP编程。而AOP的实现原理就是代理模式。

代理模式的作用就是职责(或者说功能)增强。那Spring帮我们做了哪些增强呢?上图的部分其实只有CRUD的业务代码,事务方面一行都没有。而没Spring之前,我们是要这样写的:

try {    
    //加载驱动
    Class.forName("com.mysql.jdbc.Driver");    
    //建立连接     
    Connection con = DriverManager.getConnection()   
    //开启事务    
    con.setAutoCommit(true/false);    
    //真正的、我们每天都要写的:)    
    C R U D    
    //提交事务    
    con.commit()
} catch (Exception e){   
    //失败了还要自己手动回滚    
    con.rollback();
} finally {    
    //关闭连接    
    conn.close();
}

写的伪代码,不用在意细节。重要的是如果每个需要用到事务的方法都要这么重复写,我们估计早疯了。这里Spring就将这些重复的代码使用动态代理给我们抽到代理类中去了,所以我们只用关注CRUD就成——从这个角度看,一定程度上造成了程序员的懒惰,不是我们不学习,只怪Spring太好用。

代理模式就不继续发散了,后面会精讲。

桥接模式

让我猜猜看,是不是有些同学第一次听到这个模式?猜对了待会下面给我留言点赞统计喔,但其实,我们每天都在用。咱们来看一段代码:

//加载驱动    
    Class.forName("com.mysql.jdbc.Driver");    
    //建立连接     
    Connection con = DriverManager.getConnection()

没错,大炮又套娃了,用的就是上面的连接数据库的代码。大家每天都要连接数据库的吧。

先铺垫一个知识: Java 没有真正能操作数据库的方法,只是提供了一些抽象的接口,真正的实现在各个数据库厂商提供的相应jar包内。mysql就由mysql的开发者提供,oracle的就由oracle的提供。

为什么我们这个DriverManager能直接get到Connection呢,就是使用了桥接模式。关键就在于这个Class.forName("com.mysql.jdbc.Driver"),Class.forName 只是加载了一个类而已,怎么做到的呢。我们来看这个Driver类到底干啥了:

3U7Njy6.png!mobile

其实就是一个静态块里面调用了 java .sql.DriverManager 的 registeDriver方法,然后把 mysql 的Driver对象传了过去,起到了一个搭桥的作用。

多的就不扩展了, 桥接模式的作用就是连接了一个抽象维度和一个实现维度 。这里的抽象维度就是指 java,实现维度就是指 mysql。其实也可以说桥接的精髓在于“约定”,java 和 各数据库厂商约定:你们底层实现我不管,但是你们得按照我的规矩来,调用我DriverManager的registerDriver方法才行。

中介者模式

聊完桥接模式,不得不聊下中介者模式了。桥接是关注两个维度的连接,而中介者是关注多个多个维度。作用是 统一管理多个多个维度的网状资源 。没错,可能你已经想到了,我们天天都在用的 注册中心就是一个中介者 ,比如 eureka、zookeeper。

QbMN7n2.png!mobile

如图,所有的 service 都只和 Service Registry 耦合,而不是直接去依赖其他service ,中介者完美地解决了网状依赖的复杂关系带来的代码混乱。总体来说中介者还是个比较容易理解的设计模式,这里就不做太多展开了。

解释器模式

解释器模式是个非常简单的模式,有种自娱自乐的味道,如果你知道它是怎么自娱自乐的,那你也能跟着“娱乐一下”。来看一个熟悉的东东:

#每隔五秒执行一次times = */5 * * * * ?

你的配置文件是不是也有corn表达式呢,这就是一个解释器模式的体现。可能现在你的内心又:

biEZz2I.png!mobile

解释器模式定义了一种表达式(或者说是语言),并且也提供了解析表达式(语言)的方法。类似的还有正则表达式。Spring中的ExpressionParser也用到了解释器模式,有兴趣的同学可以去看看源码。

策略模式

策略模式也比较容易理解,大家在开发和日常生活中也经常用到,也是一个需要重点掌握的设计模式。每天买东西,使用手机支付,就是策略模式的一种体现。 策略模式只注重结果,不管使用哪种策略(策略模式要求策略的数量是有限的),最后的结果都是一样的。 比如手机支付,不管使用哪种支付方式,该付多少还是得付多少。

JJbAzeI.png!mobile

原型模式

原型模式也是一种非常简单,唉,凌晨一点多了。明天继续写吧。困了困了。

y2M3u2f.png!mobile

诶大炮又复活了。原型模式也是一种非常简单的设计模式: 给定一个对象实例,通过拷贝的技术创建新的对象,达到对象复用的效果 。来看一段代码:

UserVo userVO = new UserVO();User user = mapper.selectByPrimaryKey(id);if(user == null){    throw new Exception("未获取到指定用户");}/* 拷贝数据,传给前端大佬渲染页面 */BeanUtils.copyProperties(user,userVO);return userVO;

是不是非常眼熟了,可能有些读者内心纷纷表示“是我本人没错了”。这里的BeanUtils.copyProperties 方法就用到了原型模式里面的 浅拷贝。

稍微提一句,不要使用Apache的 BeanUtils 来copy。性能问题比较严重,在《阿里巴巴Java开发手册》中也有专门提到。如果需要切到Spring的copy方法,也有个坑需要注意下,这两家的提供的copy,类名相同、方法名也相同。但 参数的位置相反 ,切换时请多加注意并做好测试。

迭代器模式

迭代器模式大家也天天都在用,直接上代码:

uUN7Rr.png!mobile

这是一段生成签名时,拼接参数的算法。里面的 Iterator 就是迭代器模式的体现,给集合提供了一个全局访问点,通过hasNext()判断没有下一个节点,next()方法获取下个节点,直到所有节点都被访问过。我们常用的 HashMap ,ArrayList 顶层都是 Collection,而 Collection又继承了 Iterator 接口。来张ArrayList 的类图感受下:

yeq2ame.png!mobile

小结

大炮稍微给大家缕了一遍,有些同学是不是觉得自己又行了,原来不知不觉间,自己竟然用了这么多设计模式,却不自知。其实还有很多,比如:

  • 责任链模式,Spring security、Shiro这些权限框架都有用到。
  • 观察者模式,Spring 的 ContextLoaderListener 就有用到。
  • 模板模式,Spring 中的 JdbcTemplate 有用到。

设计模式无处不在,平时开发我们也无时无刻在享受着设计模式带来的好处,只是有些同学缺乏一个系统的认知。由于篇幅原因就不再过多介绍了。

qEzYzuJ.png!mobile

好吧,标题党不算太过分,超了三分钟嘻嘻。

下期预告

今天讲的还是比较片面和粗犷的,很多细节都没有体现出来。这一期相当于是个设计模式的面试尝鲜版,下一期会对设计模式做一个正式的介绍:

  • 为什么要用设计模式
  • 设计模式是什么
  • 设计模式有哪些
  • 设计模式的分类

大概就是上面这些了。然后就开始做具体某个模式的详细分析和代码实现(想先看什么模式记得私信我喔)。

今天就先到这了,下次去面试被问到用过哪些设计模式,心里是不是就有底了,直接给他一顿连招13种统统安排上。我们下期见。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK