设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观
一、单列模式
问题1:什么是单列模式?
---------保证一个类只有一个实例,并且提供一个访问该全局访问点
问题2:单列的应用场景?
---------数据库连接池
---------任务管理器
---------回收站
---------Web应用的配置对象的读取
---------计数器
---------HttpApplication 也是单位例的典型应用
---------等等…
问题3:单列的优缺点?
优点:
--------- 1.在单例模式中,活动的单例只有一个实例,可以确保所有的对象都访问一个实例
--------- 2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
--------- 3.提供了对唯一实例的受控访问。
--------- 4.由于在系统内存中只存在一个对象,因此可以节约系统资源,避免对共享资源的多重占用
缺点:
--------- 1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误
--------- 2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
--------- 3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
--------- 4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
--------- 5、如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
问题4:我该使用什么模式?
--------- 如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
--------- 如果需要延迟加载,可以使用静态内部类或者懒韩式,相对来说静态内部类好于懒韩式。
1.1、饿汉式
类初始化时,会立即加载该对象,线程天生安全,调用效率高
优点:
不会被反射入侵
/**
* 饿汉式
*/
public class SingletonDemo01 {
/**
* 类初始化时,会立即加载该对象,线程天生安全,调用效率高
*/
private static SingletonDemo01 singletonDemo01 = new SingletonDemo01();
private SingletonDemo01() {
System.out.println("初始化");
}
/**
* 获取类的实例对象
*/
public static SingletonDemo01 getInstance() {
return singletonDemo01;
}
}
测试调用
public static void main(String[] args) {
SingletonDemo01 s1 = SingletonDemo01.getInstance();
SingletonDemo01 s2 = SingletonDemo01.getInstance();
System.out.println(s1 == s2);
}
1.2、懒汉式
类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
优点:
使用时才初始化
/**
* 懒汉式
*/
public class SingletonDemo02 {
/**
* 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
*/
private static SingletonDemo02 singletonDemo02;
private SingletonDemo02() {
System.out.println("初始化");
}
/**
* 当实例对象为空,创建实例对象,会有线程安全问题,两个线程同一时间执行会重复创建对象
*/
public synchronized static SingletonDemo02 getInstance() {
if (singletonDemo02 == null) {
singletonDemo02 = new SingletonDemo02();
}
return singletonDemo02;
}
}
测试调用
public static void main(String[] args) {
SingletonDemo02 s1 = SingletonDemo02.getInstance();
SingletonDemo02 s2 = SingletonDemo02.getInstance();
System.out.println(s1 == s2);
}
1.3、枚举
/**
* 使用枚举实现单例模式
* 优点:实现简单、枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞
* 缺点没有延迟加载
*/
public class User {
/**
* 使用/获取枚举创建的实例方法
*/
public static User getInstance() {
return SingletonDemo04.INSTANCE.getInstance();
}
/**
* 使用枚举创建实例
*/
private static enum SingletonDemo04 {
INSTANCE;
// 枚举元素为单例
private User user;
/**
* 创建实例
*/
private SingletonDemo04() {
System.out.println("初始化");
user = new User();
}
/**
* 返回实例
*/
public User getInstance() {
return user;
}
}
}
测试调用
public static void main(String[] args) {
User u1 = User.getInstance();
User u2 = User.getInstance();
System.out.println(u1 == u2);
}
1.4、静态内部类
优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象
/**
* 静态内部类方式
*/
public class SingletonDemo03 {
private SingletonDemo03() {
System.out.println("初始化..");
}
/**
* 创建实例
*/
public static class SingletonClassInstance {
private static final SingletonDemo03 singletonDemo03 = new SingletonDemo03();
}
/**
* 调用返回实例
*/
public static SingletonDemo03 getInstance() {
return SingletonClassInstance.singletonDemo03;
}
}
测试调用
public static void main(String[] args) {
SingletonDemo03 s1 = SingletonDemo03.getInstance();
SingletonDemo03 s2 = SingletonDemo03.getInstance();
System.out.println(s1 == s2);
}
1.5、双重锁
就是在懒汉式的基础上在加一把锁,处理线程安全问题
public class SingletonDemo04 {
private SingletonDemo04 singletonDemo04;
private SingletonDemo04() {
}
public SingletonDemo04 getInstance() {
if (singletonDemo04 == null) {
//锁
synchronized (this) {
if (singletonDemo04 == null) {
singletonDemo04 = new SingletonDemo04();
}
}
}
return singletonDemo04;
}
}
避免反射攻击
在构造函数中,只能允许初始化化一次即可
private static boolean flag = false;
private SingletonDemo04() {
if (flag == false) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static void main(String[] args) {
}
二、工厂模式
2.1、简单工厂模式
说明:
相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。
创建示例流程:
1、创建业务接口类,编写一个接口方法
2、创建N个接口实现类,并实现接口方法
3、创建Factory 工厂类,并根据不同的参数判断,来判断调用接口实现方法
4、消费测试
2.2、工厂方法模式
说明:
工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
创建示例流程:
1、创建业务接口,添加业务逻辑方法接口
2、创建实现业务接口的方法类,(图中示例为3个)
3、创建工厂接口,添加接口方法,返回值为业务接口类名
4、创建实现工厂接口的方法类,返回值为每个工厂对应的接口实现类名 , (图中示例为3个)
5、消费者创建根据需求创建工厂实例,调用具体工厂接口实现类的方法获得返回的具体工厂实例
6、调用业务实现接口
---- ,
每个工厂下的业务逻辑实现类可以和简单工厂一样有无数个,a品种苹果,b品种苹果,c…
2.2、抽象工厂模式
说明:
抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。
创建示例流程:
1、创建业务逻辑的通用接口抽象类,并创建抽象方法,1、生产发动机,2、生产座椅
2、创建产品族a 和 b 的抽象类 --> 继承通用接口抽象类
3、创建产品族a 和 b 的实现类,并按需实现抽象类接口 (如图,暂时可以定义2个)
4、创建抽象工厂,并创建抽象方法,1、生产发动机,2、生产座椅(方法返回值=产品族接口名)
5、创建具体工厂a 和 b 继承抽象工厂,并返回具体的工厂实例对象( return new 产品族具体实现类())
6、根据需求调用获取工厂对象,通过工厂对象调用具体的实现类
-------- 如:
我现在需要生产一辆车,现在有两个厂家的零件
我需要a 厂商的发动机和 b 厂商的座椅
1.通过工厂直接获得a 厂商的发动机实例对象 ( new 生产发动机a() )
2.通过工厂直接获得b 厂商的座椅实例对象 ( new 生产座椅b() )
3.调用抽象接口业务逻辑的具体实现就ok了
三、代理模式
说明:
类似于 个人 --> 中介 --> 房东
个人所有需求都需要通过中介,谈好了直接中介通知房东,房东ok了直接和中介签合同就完了
应用场景:
– 1、事务
– 2、spring 环绕通知
– 3、spring 切点切面
– 4、日志打印等等
3.1、静态代理模式
说明:
每个类都需要添加一个代理类,每个方法也需要单独处理,不现实
实现流程:
1、代理类继承和实现类一样的接口
2、代理类调用实现类的接口方法,并在调用方法前后做处理
如图:
创建代理类 xxxProxy,消费者只能调用xxxProxy,不能直接调用实现类
3.2、jdk 动态代理
说明:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
原理:
是根据类加载器和接口创建代理类(此代理类是接口的实现类,所以必须使用接口 面向接口生成代理,位于java.lang.reflect包下)
代码示例:
/**
* 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对
*/
public class InvocationHandlerImpl implements InvocationHandler {
// 这其实业务实现类对象,用来调用具体的业务方法
private Object target;
// 通过构造函数传入目标对象
public InvocationHandlerImpl(Object target) {
this.target = target;
}
/**
* 处理调用方法前后任务
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("调用开始处理");
// 执行调用的方法
result = method.invoke(target, args);
System.out.println("调用结束处理");
return result;
}
}
使用:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 被代理的对象
IUserDao userDao = new UserDao();
InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
// 获取类加载器
ClassLoader loader = userDao.getClass().getClassLoader();
// 获取接口
Class<?>[] interfaces = userDao.getClass().getInterfaces();
// 主要装载器、一组接口及调用处理动态代理实例
IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
// 调用代理类的 save方法
newProxyInstance.save();
}
3.3、CGLIB 动态代理
说明:
使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码
pom.xml
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
代码示例
public class CglibProxy implements MethodInterceptor {
private Object targetObject;
/**
* 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
*/
public Object getInstance(Object target) {
// 设置需要创建子类的类
this.targetObject = target;
Enhancer enhancer = new Enhancer();
//创建虚拟子类
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 处理调用方法前后任务
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事物");
Object result = proxy.invoke(targetObject, args);
System.out.println("关闭事物");
// 返回代理对象
return result;
}
}
使用:
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
userDao.save();
}
四、建造者模式
说明:
建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性
如:
1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
2、JAVA 中的 StringBuilder。
应用场景:
1、需要生成的对象具有复杂的内部结构。
2、需要生成的对象内部属性本身相互依赖。
与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
创建示例流程:
这里以游戏开发中人物的构造过程为例。在游戏中创建一个形象时,需要对每个部位进行创建。简化而言,需要创建头部,身体和腿部。
1、创建角色类 Role
2、创建建造者抽象接口,提供如图提供4个方法
3、创建具体建造者
4、创建指挥官
------- 1、在客户端 new 指挥官对象的时候需创建建造者对象( 默认构造器中new 建造者抽象接口())
------- 2、创建-建造拼装各身体部位任务方法
5、客户端创建建造者对象传入指挥官对象中,指挥官类中初始化建造者对象( 4.1), 并执行拼装任务
不会创建可参考:
http://c.biancheng.net/view/1354.html
五、模板方法模式
说明:
处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用工厂方法模式,将这个节点的代码实现转移给子类完成。即:处理步骤在父类中定义好,具体的实现延迟到子类中定义。
说白了,就是将一些相同操作的代码,封装成一个算法的骨架。核心的部分留在子类中操作,在父类中只把那些骨架做好。
应用场景:
1、数据库访问的封装
2、Junit单元测试
3、servlet中关于doGet/doPost方法的调用
4、Hibernate中模板程序
5、spring中JDBCTemplate,
6、HibernateTemplate等等
创建示例流程:
1、创建模板方法抽象类,并编写如图,1,2,3,4方法,其中1,3,4为具体方法,并在抽象类实现,2为抽象方法,由子类实现
2、创建子类,继承模板方法抽象类,并根据不同的对接方式实现不同的对接流程
3、业务层创建对象,new 具体实现类,抽象类对象接收,获得具体实现的对象
4、依次调用1,2,3,4方法,3,4,步骤都是在不同业务逻辑的后方,可以在抽象层在定义一个方法5,直接调用3,4 方法,依次调用1,2,5方法也是一样的
六、适配器模式(adapter pattern)
说明:
在设计模式中,适配器模式(英语:adapter pattern)有时候也称包装样式或者包装(wrapper)。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
适配器分类:
类适配器、对象适配、接口适配方式
类适配器方式采用继承方式,对象适配方式使用构造函数传递
应用场景:
1、OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,即:将一个字符流的输出对象变为字节流的输出对象。
2、InputStreamReader:是Reader的子类,将输入的字节流变为字符流,即:将一个字节流的输入对象变为字符流的输入对象。
3、SpringMVC 适配器
创建示例流程:
1、创建安卓,苹果充电接口
2、创建impl 实现安卓,苹果充电接口
3、创建适配器类,继承苹果充电接口,并添加构造器,传入安卓充电接口对象使其支持安卓充电口(obj = new 安卓充电接口())
4、添加手机充电类,添加充电方法
---- 4.1、使用安卓充电口给苹果手机充电,安卓充电接口 obj = new 适配器(new 安卓充电实现())
---- 4.2、使用安卓充电口给安卓手机充电,安卓充电接口 obj = new 安卓充电实现()
---- 4.3、使用苹果充电口给苹果手机充电,苹果充电接口 obj = new 苹果充电实现()