单例模式
- 使用场景:有些情况下,我们只需要一个实例,比如说:点击按钮之后的弹框,应该是点击多次也只弹一次,而不是点击一次弹一次。这种情况就需要用到单例模式,从功能上讲,单例模式用于那些只需要一个实例的场景;从资源上讲,单例模式可以避免对象频繁的创建和销毁。
- 如何实现:构造方法私有,通过
getInstance()
方法来获得实例。 - 实现代码
懒汉式,线程不安全
所谓懒汉式,就是说并没有在定义
intance
的时候就给它初始化,因为懒,只有到第一次用到这个实例的时候才去初始化。懒汉模式是一种lazy-loading,只有到用的时候才会创建,从而也就会有个问题,第一次使用的时候创建可能会比较耗时。如果我们知道这个对象肯定是要被用到的,可以用饿汉模式,下面会说到。但是很显然在多线程环境下是会出问题的,如果两个线程同时执行到了
if(instance==null)
,然后发现都成立,就会new出两个实例来。
1 | public class SingleObject{ |
- 懒汉式,线程安全
这种方法在第一次调用时才初始化,避免浪费内存。但是需要加锁才能保证单例,会影响效率。因为只有new的时候,也就是第一次初始化的时候才需要并发控制,其他情况下是不需要并发控制的,但是给这个getInstance()
方法加上synchronized
关键字,会导致每次需要获得单例的时候都会被加锁。
1 | public class SingleObject{ |
可以看出这种方式一个问题就是,锁的粒度太大了,其实只需要在new对象的时候加锁就可以了,这就引出了下面双重校验锁。
- 饿汉式,线程安全
所谓饿汉,就是说在类加载的时候就初始化。它这里其实是利用了类加载机制,类加载的时候是会用synchronized
关键字给加锁的。未实现lazy-loading.
1 | public classs SingleObject{ |
- 饿汉变种
1 | public class Singleton{ |
同样是利用类加载机制,同样是饿汉,但是它实现了lazy-loading。类加载的时候Singleton被装载,但是instance不一定被初始化,只有调用getIntance()
方法的时候,才会显式装载SingletonHolder
类,从而实例化instance
- 双重校验锁
1 | public static SingleObject{ |
double check在代码中已经体现的很明显了,这里主要解释一下volatile
关键字。在jvm的文章中,我们已经提到volatile
关键字可以保证可见性、禁止指令重排序。它用在这里的的主要作用也是为了保证禁止指令重排序,在初始化一个实例的时候,要经过一下几个步骤
(1)申请内存空间
(2)初始化默认值(注意不是构造方法的初始化)
(3)执行构造方法
(4)连接引用和实例
其中步骤(3)对应new SingleObject()
,步骤四对应instance=new SingleObject()
,这四个步骤可能会进行指令重排序,变成(1)(2)(4)(3),这种情况下,上述代码执行完后可能会return一个还没有初始化完的实例,另一个线程获取时,就会获取到一个未初始化完的对象。而使用了volatile
关键字后,可以禁止指令重排序,那么执行的顺序就是(1)(2)(3)(4),就不会存在上述问题。
- 静态内部类
1 | public class Singleton { |
- 枚举
- CAS
Iterator
Iterator本身是个Interface,被很多集合类所实现,我们也可以自己实现。
使用Iterator的一个好处就是我们可以不用去关注序列本身具体的结构,只用操作Iterator就可以实现序列的遍历。
如何实现
在集合类内部实现一个Iterator接口,在这个实现的接口里面对集合类进行访问。而外部类想要访问集合中的元素时,只需要给它返回一个Iterator对象,然后通过Iterator的next()和hasNext()方法来访问集合元素。
这样做的一个好处:可以不对外部类暴露集合内部情况。
这里是代码部分
其实感觉设计模式的核心就是实现功能的解耦,让每个部分各司其职,而不是一锅大杂烩。
Visitor
当对象结构对应的类很少改变,但经常需要在此结构上定义很多不同且不相关的操作,为了不让这些操作污染这些对象的类,我们将这些操作封装在另一个类中,而在被访问的类中只提供一个接待访问者的接口accept(Visitor),在Visitor()中传入被访问类的引用供访问。
类似于有人来你家做客,你只负责给他开个门(被访问的类提供一个accept()接口让visitor进来),他来到了你家,对你家的结构很清楚了(visit
方法有一个被访问对象作为参数),至于客人具体要做什么,完全自便(具体的visit()方法的内容由Visitor定)。
这种设计的好处:
- 很好的解耦了访问者和被访问者
- 给被访问者提供了很大的自主操作的空间。
ComputerPart 接口
1 | package Visitor; |
ComputerPart实现类
1 | package Visitor; |
Visitor接口
1 | package Visitor; |
Visitor接口实现类
1 | package Visitor; |
当我们需要访问更多对象时,可以在Visitor中多重载几个visitor函数即可。
Composite
也叫做部分-整体模式,把对象组合成树形结构,将个体对象(叶子)和组合对象(树枝)统一对待。
它是一种嵌套的关系,就像目录树一样,一个文件夹套一个文件夹,直到最后的叶子节点——文件;或者像一句话一样,从句子到单词再到最后的字母;还有HTML标签,等等。
当我们想忽略组合对象与个体对象的不同,统一使用组合结构中的所有对象时,可以考虑这种设计模式。
Implementing the composite pattern lets clients treat individual objects and compositions uniformly.
如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component
一般来讲,组合模式的Composite里面会有一个Arraylist
,add方法既可以向其中添加Leaf,也可以添加composite,这里就体现了个体和组合对象的统一对待。
这里是相关代码实现,代码中除了add方法能体现该思想外,printList
方法也能体现,对于File的print,只是简单的打印,对于Directory的print则是通过迭代器,去打印其每一个item,这里其实是个递归调用,如果item是file,那么就调用了File的·printList方法,如果item是directory,则递归调用。
Adapter
适配器模式,顾名思义,就像电源适配器一样,你需要对已有的东西进行一定的改装,使他适配于新的需求。
比如说我原本就有一个现成的类,现在有一个新项目,也能用到这个类,但是需要稍作修改(旧类的接口不符合新系统的需要)。
再具体一点,新类A想用旧类B中的一些方法,但是得需要修改一下,因为这些方法不能完全满足现在的需求。有这么几种想法:
直接修改旧类B (这个方法很暴力)
新类A继承旧类B (Java中类只能单继承,而且这两个类逻辑上也不是继承关系,如果继承并覆盖的话还不如重新实现)
适配器模式 (通过一个中间件把类A的东西转化成类B的)
适配器模式就像生活中的电源适配器一样,能够连接两头并且进行转换。举个栗子,比如说有个Chinese类,他能speak和write,但是都是中文,后来,需求改变,又需要再写一个American,他也能speak和write,但是是英文。那么问题来了,如果重新写一个类的话,其实很多代码都是和Chinese相同的,这时候就需要适配器模式了。适配器模式有两种实现方法
- 继承已有类,实现目标接口
- 依赖已有类(将已有类作为自己的元素),实现目标接口
这里实例一个依赖的实现
Chinese类
1 | Public class Chinese{ |
American接口
1 | Public interface American{ |
适配器
1 | Public class TranslatorAdapter implement American{ |
测试类
1 | Pulic class Test{ |
Template Method Pattern
Template Method模式用到了抽象类,用一句话总结它的特点就是:父类中定义处理流程的框架,子类中实现具体的处理。模板顾名思义,就是提供了一个模板,最后的模具和它的形状相同(子类继承父类),但是但是不同的模具用的材料可能不同(继承自抽象父类的子类的实现方法不尽相同)
这里是一个简单的demo,需要注意两个问题
- 模板使用final关键字修饰,不可改变
- 具体方法延迟到子类中去实现
Simple Factory Pattern
模式动机
假设这样一种场景:有一个父类按钮Button, 这个父类按钮底下继承了很多不同形状的子类按钮,圆形的,正方形的,三角形的。。。现在客户端程序想要用这些按钮,一种方法是你说明要哪种按钮,然后在客户端中new出来,但是显然这样耦合度比较高,因为在客户端中我们想用就完事了,而不想多余的先判断,再new. 另一种可行的办法是,我们再重新建一个类,叫做工厂类,当客户端中想要什么类型的按钮时,就跟工厂类讲,比如客户端需要一个圆形按钮,它不需要知道这个按钮的类名叫什么,它只需要告诉工厂类,我需要一个圆形按钮,工厂类造好给它就可以了。
这就是简单工厂模式,就是说有一个专门的工厂类用来生产一堆大差不差的子类,外部只需要告诉它要什么类,它生产好了再交给外部。
UML
再偷一张菜鸟教程的图嘿嘿嘿
这个很容易理解,就不写代码了
要说简单工厂模式和策略模式的区别,工厂模式生产的是类,而策略模式注重的是策略,也就是方法,
工厂类根据不同的需求返回不同的类,策略模式根据外部传入的不同的类,给出(执行)不同的方法(策略)
工厂方法模式
上述简单工厂模式中,有一个工厂类,客户端告诉工厂需求,它根据需求创建好对应的类并返回。这就存在一个问题,你需要在工厂类中判断当前是哪种需求,这就意味着以后要扩展时,你肯定要在工厂类中做修改,增加新的需求判断条件,这就违背了开闭原则,因为,我们提出了工厂方法模式。
简单工厂模式只有一个工厂类,根据不同需求产生不同的product, 工厂方法模式有多个工厂类,不同的需求对应不同的工厂类,这些工厂类实现了共同的接口。当日后要扩展时,只需要添加product类,添加对应工厂类就可以了。这种修改同时意味着我们将需求的判断交给了客户端,也就是说,客户端需要告诉工厂类它想要什么具体产品,而不是像简单工厂模式那样这个判断是在工厂类中进行的。
抽象工厂模式
工厂方法模式中,一个工厂生产一种特定的产品,工厂方法也具有唯一性。但是也有一种情况,一个工厂要生产多种产品,如一个海尔公司要生产海尔电视,海尔冰箱,海尔洗衣机;又如一个SqlServer工厂要生产user表,部门表等,一个Access工厂也要生产user表,部门表等。对于这种一个工厂要生产多种不同产品的情况,我们采用抽象工厂模式。
Decorator
装饰器模式,在不违背开闭原则的基础上,给一个类动态的添加功能。也就是说在不修改这个类且不影响其他类的基础上,给这个类添加功能。
对于一个设计模式,理解它的原理是很简单的,但是要理解它为什么是这样的,应该在哪些场景中使用它,却是不容易的。下面我将试着从一个开发者的角度去思考这个问题。
假如有一个类 A,现在需求升级,要给A添加新的功能,该怎么做?
- 直接修改A,简单粗暴,但同时也有很多隐患。
- 写一个子类继承A,在子类中添加新的功能。可以是可以,不够优雅,有新功能的A和原来的A原则上讲并没有继承关系,只是我们为了实现需求给强行继承了。
- 使用装饰器模式。
装饰器模式,听名字就知道,它装饰了原来的类。就像明月装饰了你的窗户,你装饰了别人的梦。抛去这些花里胡哨的名词,我们来看他的本质。
既然是装饰,其实可以理解为一种“包裹”,我在原来的类的基础上再包裹一些东西。那么问题来了,怎么包裹呢?首先,为了让外人看不出我的装饰器类和原来的A类的差别,我让装饰器类和A类继承自同一父类;
然后,为了实现包裹,我把原来的A类作为装饰器类的一个构造函数参数传给装饰器类,这样装饰器类既能够使用A类,又能够在A类的基础上进行一些操作了。(这个包裹说的专业点就叫做聚合)
以上。
从UML图可以看出,其实Decorator和ConcretComponent
继承自同一父类,Decorotor
它聚会了Component类,然后在复写的operation
方法中对聚合而来的component
类进行操作,这就是所谓的装饰。
其实装饰器模式的本质就是,在一个装饰器类中引入了原本要修改的类,然后对这个类进行装饰。要被修改的类的代码没有发生改变,只是它自己被放在另一个类中被操作了一波。
它的核心思想:1. 继承自相同的父类(这样就可以复写要修改的类中的方法) 2. 聚合了要修改的类(这样就可以在原来类的基础上进行一定的装饰,而这个装饰代码是在原来的类之外的。)
Proxy Pattern
代理模式,故名思意。加入我们想访问对象A,代理模式就是在我们和对象A之间建立一个中间层,也就是代理,从而让代理替我们去访问这个对象。
它的核心思想就是让代理和被访问对象实现同一接口,然后在代理中调用被访问对象中的方法来实现访问。
原型模式
Java中原型模式通过实现`Cloneable`接口来实现。所谓原型模式就是说,这个接口有个clone方法,当我们需要这个对象时,通过clone方法来获得对象,而不是通过类的构造函数来构造类,这两者的代价是很不同的。
所使用的场景:当直接创建对象的代价比较大时,则采用这种模式。
所以这里就会存在一个浅复制和深复制的问题。
外观模式
假如你接手祖传代码后,使用起来肯定不容易,这时候你可以在祖传代码之上再加一个中间层,外部代码通过访问中间层来实现对祖传代码的访问,这样就避免了外部代码直接去访问祖传代码。
外观模式隐藏系统的复杂性。它向现有的系统提供一个接口,来隐藏系统的复杂性。客户端不需要知道系统内部的复杂联系,整个系统只需要提供一个接待员即可。
关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
建造者模式
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。(感觉有点像模板模式)
建造者模式更加关注零件装配的顺序。就是说组件不变,而这些组件之间的组合会发生变化。
如何实现:首先有个builder
类,用来有构建各个组件的方法,其次有个Director
类,用来描述各个组件之间的组合关系。
观察者模式
一个对象的状态发生变化,给所有依赖它的对象发送通知,进行更新。
策略模式
设计模式的终极目的在于解耦。
加入完成一个任务,有多种方法,我们希望根据不同的环境或者不同的条件这多种方法可以随时替换。
比如说现在有个计算器,有时候我们要它做加法,有时候又要它做减法,那么怎么实现呢?
最直观的方法就是硬编码了,直接把这些加减乘除写成一个计算器Calculator的方法,如果这样的话后期修改也就比较麻烦。
策略模式的思想就在于把这些不同的算法封装成不同的策略类,然后聚合到Calculator类中,然后再给Calculator类一个set方法,以此实现可插拔。
再盗一张菜鸟教程的图。