一、单例模式
采取一定的方法保证在整个软件系统中对某个类只能存在一个对象实例,并且该类就只提供一个获取其对象实例的方法(静态)
如Hibernate中的SessionFactory,充当数据源的代理,并负责创建Session对象。一个项目通常只需要一个SessionFactory
(1)饿汉式(静态常量)
首先构造器私有化,构造器不需要实现任何东西(防止new),在类的内部创建对象(用final和static修饰),向外暴露一个静态获取对象的公共方法(getInstance())
优点:在类装载时就完成了实例化,避免了线程同步的问题
缺点:类装载时就完成了实例化,没有达到Lazy loading的效果,若未使用过该实例,则会造成内存的浪费
(2)饿汉式(静态代码块)
和上面的模式一样,都是在类装载的时候就实例化
(3)懒汉式(线程不安全)
优点:起到了Lazy Loading的效果,但是只能在单线程使用
线程不安全的原因:
存在某一线程进入if(instance == null)判断中,还未来得及往下执行时,另一个线程也进入当前判断语句,这时便会产生多个实例对象,不安全。
多线程中不能使用该方式
(4)懒汉式(线程安全-同步方法)
解决了线程安全问题,但每个线程在想获取实例时,执行getInstance()时都需要同步,其实这个方法只执行一次实例化代码就足够,后面想要获取实例直接return就行。效率太低
多线程中不推荐使用
(5)懒汉式(线程安全-同步代码块)
其实未能实现线程安全问题,当多个线程同时进行到if(instance == null)中时,会获取到多个实例
多线程中不能使用
(6)双重检查
使用volatile关键字修饰实例对象,在获取实例方法中,使用双重检查(两次检查if(instance == null)),解决线程安全问题,同时实现Lazy Loading,且保证了效率
推荐使用
(7)静态内部类
优点:
1.采用类装载的机制来保证初始化实例时只有一个线程
2.静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化(调用getInstance()方法),才会装载SingletonInstance类,从而完成Singleton的实例化
3.类的静态属性只会在第一次加载类时初始化
推荐使用
(8)枚举
通过枚举实现单例,不仅能避免多线程问题,而且还能防止反序列化重新创建新的对象
强烈推荐使用
在JDK中,java.lang.Runtime就是经典的单例模式
二、工厂模式
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦
创建对象实例时,不要直接采用new关键字,而是把new这个动作放在一个工厂的方法中并返回实例
不要让类继承具体类,而是继承抽象类或者实现interface(接口)
不要覆盖基类中已经实现的方法
(1)简单工厂模式
定义一个创建对象的类,由这个类来封装实例化对象的行为
由一个工厂对象决定创建出哪一种产品类的实例
披萨订购示例:
OrderPizza类中方法:
弊端:当新增一种披萨种类时,各个orderPizza类都会发生修改(再新增else if判断)
改进:使用简单工厂模式
SimpleFactory:
OrderPizza:
当新增披萨种类时,只需要在工厂中新增判断,不用修改OrderPizza的方法
关于源码:在JDK中Calender类就使用了简单工厂模式
(2)工厂方法模式
定义一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将实例化对象推迟到子类
在简单工厂模式上 针对工厂中定义的createPizza()方法:
向上定义成父类中的抽象方法,再由不同的子类去实现该方法(在简单工厂模式下多增加抽象一层)
SimpelFactory:(将OrderPizza类看做成一个工厂)
BJOrderPizza:
LDOrderPizza:
(3)抽象工厂模式
定义一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
三、原型模式
Java中Object是所有类的基类,存在clone()方法可以将对象复制一份,但需要实现clone的Java类必须实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 ==》原型模式
原型模式(prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
Sheep:
Client:
在Spring中bean的创建使用到了原型模式(getBean()方法进入源码)
浅拷贝:
使用默认的clone()方法来实现
当数据类型是基本数据类型时,会进行值传递,也就是属性值复制一份给新对象
当数据类型是引用数据类型时,会进行引用传递,就是两个变量指向同一个实例,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
深拷贝:
复制对象的所有基本数据类型的成员变量值,为所有的引用数据类型的成员变量申请存储空间(对整个对象进行拷贝)
实现深拷贝方法:
(1)重写clone()方法
实现Cloneable接口
(2)通过对象序列化实现(推荐)
实现Serializable接口
四、建造者模式
将复杂对象的建造过程抽象出来(抽象类),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象
角色:
(1)Product(产品角色):一个具体的产品对象
(2)Builder(抽象建造者):创建一个Product对象的各个部件指定的接口/抽象类
(3)ConcrateBuilder(具体建造者):实现接口,构建和装配各个部件
(4)Director(指挥者):构建一个使用Builder接口的对象,主要用于创建一个复杂的对象。隔离客户与对象的生产过程,同时负责控制产品对象的生产过程
Product:
Builder:
ConcrateBuilder:
Director:
JDK中StringBuilder使用建造者模式
五、适配器模式
将某个类的接口转换成客户端期望的另一个接口表示,目的是兼容性
(1)类适配器模式
被适配者:
适配器:
目标:
使用目标:
缺点:由于Java是单继承,则目标必须定义为接口,具有一定局限性。被适配类的方法会在适配器中暴露出来
优点:继承被适配类,可以根据需求重写其中方法,增加了灵活性
(2)对象适配器模式
与类适配器模式相同,只是将继承被适配类改变成持有被适配类对象(关联代替继承),是常用的一种
与类适配器相比,只有适配器发生了
适配器:
(3)接口适配器模式
当不需要实现接口中所有方法时,可以设计一个抽象类实现接口,并为所有方法提供一个默认实现(空方法),再通过使用该抽象类(如匿名内部类)进行选择性覆盖方法来实现
适用于一个接口不想实现其所有方法的情况
接口:
抽象类实现接口(空方法体):
使用抽象类真实实现需要的方法(如匿名内部类方式):
在SpringMVC的HandlerAdapter使用了适配器模式
六、桥接模式
将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变,保持各部分的独立性以及对他们的功能扩展
抽象类:
抽象类的子类:
接口:
实现类:
在JDBC使用使用桥接模式,Driver看做是接口,实现类有MySQL的Driver,也有Oracle的Driver
应用场景:
对于不希望继承或由于多层次继承导致类的个数急剧增加的系统
如JDBC驱动程序
银行转账系统:
转账分类(抽象层):网上转账、柜台转账、ATM转账
转账用户(实现层):普通用户、银卡用户、金卡用户
消息管理:
消息类型(抽象层):即时消息、延时消息
消息分类(实现层):手机消息、邮件消息、QQ消息
七、装饰者模式
动态的将新功能附加到对象上。在对象扩展方面,比继承更有弹性,装饰者模式也体现了开闭原则(OCP)
装饰者模式现实实例,如打包一个快递:
被装饰者:陶瓷、衣服等货物
装饰者(Decorator):塑料泡沫、报纸、木板等
被装饰者和装饰者被称为主体,将主体设计为抽象类,被装饰者(具体类ConcrateComponent)和装饰者继承主体(Component)
装饰者类中聚合主体对象(Component),即具体被装饰者类(ConcrateComponent)可以在装饰者中使用
若被装饰者具体类(ConcrateComponent)类很多,在继承主体(Component)之间还可以增加缓冲层,将共有部分提取出来,抽象成一个类
由装饰者类去包裹被装饰者类,采用递归
主体:
缓冲层:
具体被装饰者:
装饰者:
具体装饰者:
最终使用装饰者:
Java的IO结构,FilterInputStream就是一个装饰者
八、组合模式
创建了对象的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系
组成部分:
Component:组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为(空方法体),用于访问和管理Component子部件,Component可以是抽象类或接口
Leaf:在组合中表示叶子节点,叶子节点没有子节点
Composite:表示非叶子节点,用于存储Component子部件,实现Component中定义的相关操作(接口)
由于存在继承(Composite继承Component),在Composite中直接聚合Component对象(父类)就可以实现Composite之间的聚合关系。
Component:
Composite:
Leaf:
Java中集合类的HashMap使用了组合模式
九、外观模式
通过定义一个一致的接口(界面类),用于屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
外观类(Facade):为调用者通过统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给恰当子系统对象
调用者(Client):外观接口的调用者
子系统的集合:指模块或者子系统,处理Facade对象指派的任务,是功能的实际提供者
外观类:
子系统:
调用者:
在Mybatis的Configuration创建MetaObject对象使用到外观模式
十、享元模式
运用共享技术有效地支持大量细粒度的对象,能够解决重复对象的内存浪费问题
常用于系统底层的开发,解决系统的性能问题(如数据库连接池)
经典应用场景:String常量池、数据库连接池、缓冲池等池技术
原理类图解释:
外部状态:指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态
内部状态:指对象共享出来的信息,存储在享元对象内部且不会随环境改变而改变(相对稳定)
抽象享元角色:
具体享元角色:
享元对象工厂:
用户(外部状态):
JDK中Integer的valueOf()方法就有使用享元模式
十一、代理模式
为一个对象提供一个替身,以控制这个对象的访问
好处:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
被代理的对象可以是远程对象、创建时开销大的对象、需要安全控制的对象
(1)静态代理
需要定义接口或父类,被代理对象(目标对象)与代理对象一起实现相同接口或继承相同父类
缺点:代理对象和目标对象需要实现相同接口或继承相同类,所以会有很多代理类,一旦接口增加方法,代理对象和目标对象都需要维护
接口:
目标对象:
代理对象:
调用:
(2)动态代理
代理对象不需要实现接口,但目标对象必须实现接口
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance()方法,参数详情见示例
注意:类图中关于ProxyFactory注释,第2点是利用反射机制
接口:
目标对象:
代理对象:
调用:
(3)Cglib代理
在内存中构建一个子类对象从而实现对目标对象功能扩展
目标对象需要实现接口,使用JDK代理
目标对象不需要实现接口,使用Cglib代理
Cglib包底层是通过字节码处理框架ASM来转换字节码并生成新的类
使用cglib代理,需要引入cglib包,代理类需要实现cglib包下的MethodInterceptor并重写intercept()方法用于执行目标对象方法
注意:代理的类不能是final修饰类,否则报错java.lang.illegalArgumentException,目标对象的方法如果为final/static,那么不会被拦截,即不会执行目标对象额外的业务方法
目标对象:
代理对象:
调用:
十二、模板模式
在抽象类中公开定义方法模板,其子类可以根据需要重写方法实现,调用时以抽象类中定义的方式进行
抽象类中模板方法一般定义为final修饰,避免子类修改方法流程
钩子方法:默认不做任何事,子类可根据情况要不要覆盖钩子方法
抽象类:
子类(未重写钩子方法):
子类(重写钩子方法):
调用:
Spring IOC容器初始化时运用到模板方法模式