面向对象的程序应该具有可维护性,代码可复用性,扩展性以及灵活性。为了实现以上目的,前辈们从实践中总结出了一套可套用的武功招式,这就是设计模式。使用设计模式可以让你写出一手令人赏心悦目的代码。
我认为每一个后端开发者都应该学习设计模式,它是代码的精华,是程序发展的强力支撑,是能够让你发出惊叹的神来之笔。
本课程共有 10 篇,结合作者的开发经验,从理论到实战,剖析设计模式经典案例,帮助读者掌握将设计模式应用于实际项目开发的能力。
课程主要分为两大部分:
第一部分(第01-09课),介绍常用的几种设计模式,通过具体案例的分析与代码实现带领大家深入学习与理解设计模式;
第二部分(第10课),将结合多种设计模式手把手带领大家开发一个综合案例,提升设计模式实战的能力。
周君,资深后端工程师,CSDN 博客专家,精通 PHP、Java、Web 开发。多年实战经验,热衷分享。
设计模式( Design Pattern )代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长一段时间的试验和错误总结出来的。
上面的解释来自于网络,是比较标准的定义,可以从中筛选出几个关键字来帮助我们理解什么是设计模式:
从上面的三个关键词中可以总结出,设计模式就是在针对编码过程中遇到的问题总结出来的最佳解决方案。
那么这些问题指的是什么问题呢?面向对象的程序应该具有可维护性、代码可复用性、扩展性及灵活性,要解决的问题就是代码可维护性问题、复用性问题、扩展性问题及灵活性问题。
简单来说,设计模式就是指导你如何写出可维护、可复用、可扩展及灵活的代码。
设计模式总共有 23 种,总体来说可以分为三大类:创建型模式( Creational Patterns )、结构型模式( Structural Patterns )和行为型模式( Behavioral Patterns )。
分类 | 关注点 | 包含 |
---|---|---|
创建型模式 | 关注于对象的创建,同时隐藏创建逻辑 | 工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式 |
结构型模式 | 关注类和对象之间的组合 | 适配器模式、过滤器模式、装饰模式、享元模式、代理模式、外观模式、组合模式、桥接模式 |
行为型模式 | 关注对象之间的通信 | 责任链模式、命令模式、中介者模式、观察者模式、状态模式、策略模式、模板模式、空对象模式、备忘录模式、迭代器模式、解释器模式、访问者模式 |
上面的三种分类说明,有助于在开发时思考当前场景应该使用哪种分类。大家不一定要全部记住,有个大概的了解即可。
写出可维护、可复用、可扩展及灵活的代码是我们的目的,也是学习设计模式的理由,但是这个理由对我们来说太抽象,下面从 “ 读 ” 和 “ 写 ” 两方面来说明到底为什么要学习设计模式。
作为开发人员,不可避免地要接触其他人写的代码,有的是一些知名的库或框架,例如 Spring 、Shiro 等。但是当我们去阅读这些框架源码的时候会发现无从下手,因为类太多了,关系太复杂,而且很多类的命名看不懂,比如 xxxBuilder 、xxxStrategy 、xxxFilter 等,一个词看不懂就可能导致你直接放弃继续阅读。
如果没有学过设计模式,自然看不懂,学习设计模式可以有效地帮助你阅读代码,即便不能百分百帮到你,至少也能帮到百分之三四十。
每一个开发人员必然喷过其他人写的代码,觉得其他人的代码有的写得很垃圾,尤其是要扩展功能或者修改功能的时候,恨不得全部删掉重新写,其实在其他人看来你的代码也是如此。所以写出一手让人无话可说的代码是很有必要的,不仅可以满足你的小小成就感,也可以让你的程序更快速稳定地发展。
在一个项目组中,如果大家都学习过设计模式,那么当你阅读或修改同事写的代码时也将得心应手,少了很多麻烦。
如今网上和书上都有大量的设计模式教程,但是他们大部分都有一个共同点:仅仅使用生活中的例子。
比如前几年我第一次学习设计模式,在学到适配器模式时,教程中抛出了一个电器的插头问题:
你家插座只有三头的,但电器插头是两头的,怎么办?弄个插头适配器将两头转换成三头。
Nice,这个例子简单明了,作为新手的我瞬间明白了适配器的含义,就是在不兼容的双方中间做一层转化。但是后来发现在实际编码中根本用不上这个设计模式,因为我不会用。
生活中的例子的确可以帮助我们理解设计模式,这是毋庸置疑的,但是想要真正用好设计模式,实际项目中的案例是必不可少的,这也是我写这门课的原因,希望通过分析实际案例,能够帮到更多想要学习设计模式的同行。
下面给出几点更加具体的建议:
本课程每一篇文章都包含三大部分:
本课程将使用 Java 语言讲解设计模式,虽然设计模式与语言本身无关,但是本课程中有许多实际案例都是来自于知名的 Java 框架源码,如果没有 Java 基础,学习效果可能不佳。
除了要求 Java 基础之外,还需要了解 UML 图,如果不了解 UML,只需要知道以下几种 UML 关系即可:
参考下图:
记不住也没关系,后续课程主要使用泛化和实现这两种,先记住这两种即可,如果有遇到看不懂的再回头来看一眼。
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
一般情况下我们是将一种行为写成一个类方法,比如计算器类中有加、减、乘、除四种方法,而策略模式则是将每一种算法都写成一个类,然后动态地选择使用哪一个算法。
这里所说的算法并不是指 “ 冒泡排序算法 ” 、“ 搜索算法 ” 之类的算法,它可以是一段代码、一个请求、一个业务操作。
策略模式如图:
从上图可以看到,我们将操作封装到类中,他们实现了同一个接口,然后在 Context 中调用。
这里我们举一个计算器的例子:
此例中,为加法和减法分别创建了一个类。
其实策略不一定要命名为 Strategy ,Context 不一定要叫 Context ,可以根据实际情况自己命名,在计算器的例子中,你如果非要命名为 Strategy 和 Context ,反而让人产生疑惑。
实际代码也很简单,具体如下。
Operation 接口:
public interface Operation { public int doOperation(int num1, int num2);}
两个实现类 —— 加法和减法:
public class OperationAdd implements Operation{ @Override public int doOperation(int num1, int num2) { return num1 + num2; }}public class OperationSub implements Operation { @Override public int doOperation(int num1, int num2) { return num1 - num2; }}
计算器类:
public class Calculator { private Operation operation; public void setOperation(Operation operation){ this.operation = operation; } public int doOperation(int num1, int num2){ return this.operation.doOperation(num1,num2); }}
使用:
Calculator calculator = new Calculator();calculator.setOperation(new OperationAdd());int result = calculator.doOperation(1,2);System.out.println(result);
使用计算器类时,如果要进行加法运算,就 New 一个加法类传入,减法也是同理。
看到这里,相信大家一定会有疑惑,为什么要把加、减、乘、除四则运算分别封装到类中?直接在 Calculator 中写 add() 、sub() 等方法不是更方便吗?甚至如果要添加其他的运算方法,每次都要创建一个类,反而更麻烦。
的确,用了策略模式之后代码比普通写法多了一些,但是这里假设一种场景:把写好的计算器代码打包好作为一个库发布出去给其他人用,其他人发现你的计算器中只有加、减、乘、除四个方法,而他想增加平方、开方等功能,怎么办?
如果是用普通写法写的计算器,想要增加功能唯一的办法就是修改你写好的 Calculator ,增加平方和开方两个 method 。
可是你提供的是一个 jar 包啊,jar 包,jar…jar…jar…jar…包……
就算你提供的是源码,你希望其他人可以随意修改你写好的代码吗?一般我们发布出去的开源框架或库都是经过千锤百炼、经过测试的代码,其他人随意修改我们的源码很容易产生不可预知的错误。
如果你用的是策略模式,那么其他人想要增加平方或开平方功能,只需要自己定义一个类实现你的 Operation 接口,然后调用 calculator.setOperation(new 平方类()); 即可。
看到这里相信你已经对策略模式有了一定的好感,甚至惊叹一声:哇,还有这种操作?
顺便提一嘴,这里很好的体现了一个设计模式的基本原则:开闭原则。开闭原则说的是 ” 对修改关闭、对扩展开放 “ 。对修改关闭就是不希望别人修改我们的代码,此路不通,对扩展开放就是希望别人以扩展的方式增加功能,策略模式把开闭原则体现得淋漓尽致。
隔壁老王准备开发一个客户端框架,允许其他的开发者进行二次开发,其中有一个更换主题的功能,开发者们可以自己定义主题。老王很快就想到了策略模式,并且提供了一个默认主题 DefaultTheme :
代码:
public interface Theme { public void showTheme();}public class DefaultTheme implements Theme { @Override public void showTheme() { //此处设置主题颜色,背景,字体等 System.out.println("显示默认主题"); }}public class ThemeManager { private Theme theme; public void setTheme(Theme theme){ this.theme = theme; } public void showTheme(){ this.theme.showTheme(); }}
使用:
ThemeManager themeManager = new ThemeManager();themeManager.setTheme(new DefaultTheme());themeManager.showTheme();
看完更换主题的案例代码,你会发现跟计算器惊人地相似,没错,所谓设计模式就是前人总结出来的武功套路,经常可以直接套用。当然也要灵活地根据实际情况进行修改,设计模式想要传达给我们的更多的是一种编程思想。
这里还有一个小窍门:
themeManager.setTheme(new DefaultTheme());
在这里老王 New 一个默认主题对象,如果其他开发者加了主题,还要修改这行代码,New 开发者自定义的主题对象。根据开闭原则,我们不希望其他人修改我们的任何一行代码,否则拔刀相见。老王机智地将主题的包名和类名写到了配置文件中,利用 Java 的反射机制动态生成主题对象,因此更换主题也只要修改配置文件即可。
Shiro 是 Java 界最著名的权限控制框架之一,相信大家都不陌生。在 Shiro 中,我们可以创建多个权限验证器进行权限验证,如验证器 A、验证器 B、验证器 C,三个验证器可以同时生效。
那么就产生了一个问题,如果验证器 A 验证通过,B 验证不通过,C 验证通过,这种情况怎么办?到底算当前用户验证通过还是不通过呢?
Shiro 给我们提供了三种验证策略,就像老王默认提供了一种主题一样:
如果你不熟悉 Shiro ,看不懂上面三种策略的含义,没关系,本课程讲的是设计模式,而不是 Shiro 的使用,你只要知道 Shiro 默认为我们提供了三种策略即可。
作为开发者,在使用 Shiro 的时候,Shiro 默认的策略未必符合我们的需求,比如我们要求三个验证器中通过两个才算通过,怎么办?很简单,Shiro 这里用的也是策略模式,我们只要自定义一个 MyAuthenticationStrategy 继承 Shiro 的 AbstractAuthenticationStrategy 。咦?前面不是说实现接口吗,这里怎么是继承?变通,要懂得变通。设计模式不是一成不变的,重要的是这种编程思想。
然后在 MyAuthenticationStrategy 实现父类要求的方法,再修改配置文件将当前验证策略改为你定义的验证策略:
authcStrategy = 你的包名.MyAuthenticationStrategy
讲完上面的例子,优点已经十分明显了,那就是遵循了开闭原则,扩展性良好。
当然,权衡利弊,跟优点比起来,这些缺点都不算事儿。
阅读全文: http://gitbook.cn/gitchat/column/5b1e3647294fb04d7c22b783