设计模式
设计模式(Design pattern),是一套被反复使用、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性。
1995 年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式。
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。-->创建对象
结构型模式,共七种:[适配器模式]、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。-->对功能进行增强
行为型模式,共十一种:策略模式、模板方法模式、[观察者模式]、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、[中介者模式]、解释器模式。
1. 创建型模式
工厂方法模式
抽象工厂模式
生成器(建造者模式)Builder
Builder模式是为了创建一个复杂的对象,需要多个步骤完成创建,或者需要多个零件组装的场景,且创建过程中可以灵活调用不同的步骤或组件
原型Prototype
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
单例Singleton设计模式
是一种常用的软件设计模式。通过单例模式可以保证系统中,应用该模式的这个类只有一个实例。即一个类只有一个对象实例。
实现步骤:
1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
3. 定义一个静态方法返回这个唯一对象。
单例设计模式-饿汉式
使用类的时候已经将对象创建完毕,不管以后会不会使用到该实例化对象,先创建了再说。
public class Singleton {
// 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
private Singleton() {}
// 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
private static final Singleton instance = new Singleton();
// 3.定义一个静态方法返回这个唯一对象。
public static Singleton getInstance() {
return instance;
}
}
单例设计模式-懒汉式
是调用getInstance()方法时实例才被创建,先不急着实例化出对象,等要用的时候才实例化出对象。
public class Singleton{
private static Singleton instance;
private Singleton(){}
private static synchronized Student getInstance(){
if(instance==null)
instance = new Singleton();
return instance;
}
}
多例设计模式
是一种常用的软件设计模式。通过多例模式可以保证系统中,应用该模式的类有固定数量的实例。多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。
多例模式可以保证系统中一个类有固定个数的实例, 在实现需求的基础上, 能够提高实例的复用性。
实现多例模式的步骤:
1. 创建一个类,将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2. 在类中定义该类被创建的总数量
3. 在类中定义存放类实例的list集合
4. 在类中提供静态代码块,在静态代码块中创建类的实例
5. 提供获取类实例的静态方法
class Student{
private Student(){}
public static final Student s1 = new Student();
public static final Student s2 = new Student();
public static final Student s3 = new Student();
public static final Student s4 = new Student();
public static final Student s5 = new Student();
}
工厂方法 Factory Method Pattern
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
工厂方法是指定义工厂接口和产品接口,但如何创建实际工厂和实际产品被推迟到子类实现,从而使调用方只和抽象工厂与抽象产品打交道。
工厂方法可以隐藏创建产品的细节,且不一定每次都会真正创建产品,完全可以返回缓存的产品,从而提升速度并减少内存消耗。Integer.valueOf();
实际更常用的是更简单的静态工厂方法(简单工厂),它允许工厂内部对创建产品进行优化。
调用方尽量持有接口或抽象类,避免持有具体类型的子类,以便工厂方法能随时切换不同的子类返回,却不影响调用方代码。List.of(T...);
抽象工厂Abstract Factory Pattern
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式是为了让创建工厂和一组产品与使用相分离,并可以随时切换到另一个工厂以及另一组产品;
抽象工厂模式实现的关键点是定义工厂接口和产品接口,但如何实现工厂与产品本身需要留给具体的子类实现,客户端只和抽象工厂与抽象产品打交道
2. 结构型模式
[适配器模式] Adapter Pattern
1. 目标接口
2. 已存在实现
3. 适配(实现目标接口,继承已存在实现)
extends 实现类 implements 接口
变化: 创建适配器,实现目标接口,继承
适配器定义为抽象类,可不重写某些需要我们修改的方法,需要时继承此适配器,重写需要修改的方法
装饰器模式 Decorator Pattern
在不改变原类的基础上对类中的方法进行扩展增强
BufferedReader
实现原则:
1. 装饰者与被装饰者是同一种类型(实现相同规范)
2. 原有功能由被装饰者调用(将被装饰者的引用传入 装饰者),只需修改想修改的功能。
3. 给一个对象添加一些额外的职责。就增加功能来说,相比生成子类更为灵活。
实例:
public interface KTV {//规范
void sing();
}
public interface BaseKTV implements KTV{//被装饰者
@Override
public void sing(){
sout("sing");
}
}
public class LightingKTV implements KTV {//装饰者
private KTV ktv;
public LightingKTV(KTV ktv){//传入BaseKtv的引用
this.ktv = ktv;
}
@Override
public void sing() {
ktv.sing();//可以调用被装饰者的功能
System.out.println("lighting");
}
}
外观模式
桥接模式
组合模式
享元模式
代理模式 Proxy Pattern
代理模式:
拦截对目标类方法的直接访问,增强目标类方法的功能
代替原对象处理某些逻辑
在不修改源代码的基础上,对原对象的功能进行修改或增强
注:被代理对象可以不存在
java中有三种方式来创建代理对象:
1. 静态代理 (装饰者设计模式)
继承或实现
2. 基于JDK(接口)的动态代理-------必须是实现了接口的类才能被代理
内存中生产代理对象,运行期生成代理对象,代理类为目标类的兄弟类
Proxy.newProxyInstance(ClassLoader, Class<T> [], InvocationHandler{
@Override
Object invoke(Object proxy, Method method, Object [] args){}
})
底层:生成的代理类,实现接口所有方法,每个方法调用时经过 handler.invoke()。
接口接受代理类对象,兄弟关系不能互相转化。
3. 基于CGLIB(父类)的动态代理-------不能是最终类(没有final修饰)
产生的代理类是目标类的子类
Enhancer.create(Class, Callback)
Callback接口的子接口 MethodInterceptor
Object intercept(Object proxy, Method method, Object [] args, MethodProxy methodProxy)
proxy: Enhancer.create方法的返回值,代理对象的一个引用
methodProxy: 方法的代理对象
methodProxy.invokeSuper(proxy, args)
总结:
1.代理模式在ava开发中是广泛应用的,特别是在框架中底层原理经常涉及到代理模式(尤其是动态代理)
2.静态代理和动态代理,实际使用时还是动态代理使用的比较多。原因就是静态代理需要自行手写代码,维护、修改非常繁琐,会额外引入很多工作量。也不能很好的使用配置完成逻辑的指定,所以使用较少。
3.基于JDK和基于CGLIB,实际使用时两个都会用到。
a.在spring中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现了接口,spring就会自动选择jdk的动态代理。而如果目标类没有实现接口,则spring会使用CGLIB。
b.我们在开发时,由于基于JDK的动态代理要求比较多,更不容易实现,所以很多人习惯于统配置为使用CGLIB进行代理。也就是CGLIB更通用。
c.如果使用dubbo + zookeeper,底层进行代理时,最好配置定死使用CGLIB的方式进行代理。因为dubbo会使用基于包名的扫描方式进行类的处理,而JDK的代理类生成的包名类似于com.sup.org...格式。我们实际需要让代理类和目标类保持-样的包名,所以只有CGLIB能保持原包名不变生成代理类。
基于接口的动态代理:
通过封装一个已有接口,并向调用方返回相同的接口类型,能让调用方在不改变任何代码的前提下增强某些功能(例如:鉴权、延迟加载、连接池复用等)。
使用Proxy模式要求调用方持有接口,作为代理的类也必须实现相同的接口。
java.lang.reflect.Proxy
这是 Java 动态代理机制的主类,它提供了一个静态方法来为 一组接口 的实现类 动态地 生成代理类及其对象。
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
InvocationHandler ih)
//obj.getClass().getClassLoader()目标对象通过getClass方法获取类的所有信息后,调用getClassLoader() 方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要。
//obj.getClass().getInterfaces()获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法。
//InvocationHandler 这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类方法的处理以及访问。
public Object invoke(Object proxy, Method method, Object[] args)
//Object proxy: 方法内部创建的代理对象的一个引用,几乎用不到,可忽略
//Method method:方法内部会使用反射技术,调用被代理人使用的方法。(方法的反射对象)
//Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。
优点:
提高了开发效率;
非常的灵活,可以为任意的接口实现类对象做代理;
接口的代理对象在程序运行时 动态生成,不需要我们去实现接口;
可以为被代理对象的所有接口的所有方法做代理;
可以在不改变方法源码的情况下,实现对方法功能的增强;
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码;
动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
缺点:只能针对接口的实现类做代理对象,普通类是不能做代理对象的。(使用第三方CGLIB)
public class DaoInstanceFactory {
/**
* @param daoInterface 要代理的接口类型,如:ContactDao.class
* @param <T> 以后可以通用,还可以代理其它的DAO接口,如:StudentDao.class
* @return 返回生成的代理类,即实现类
*/
public static <T> T getBean(Class<T> daoInterface) {
/**
* 生成接口的代理对象
* 参数1:类加载器
* 参数2:所有实现的代理接口数组,如:ContactDao.class
* 参数3:回调函数的接口,接口中每个被代理的方法都会调用一次此接口中的invoke方法
*/
return (T) Proxy.newProxyInstance(DaoInstanceFactory.class.getClassLoader(), new Class[]{daoInterface}, new InvocationHandler() {
//编写每个被代理的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.通过工具类得到会话
SqlSession session = SqlSessionUtils.getSession();
//2.得到代理对象
T mapper = session.getMapper(daoInterface);
//3.调用DAO中方法,method就是调用的方法对象。它有2个参数:调用方法的对象,方法的参数
Object result = method.invoke(mapper, args);
//4.提交事务
session.commit();
//5.关闭会话
session.close();
return result;
}
});
}
}
3. 行为型模式
策略模式 Strategy Pattern
多种策略实现一个接口,用户调用时传入不同的策略,返回不同的结果
例如:
new Thread(new Runnable() {
@Override
public void run() {
...
}
})
模板方法模式 Template Method Pattern
[观察者模式]
迭代器模式
责任链模式 Chain of Responsibility Pattern
如:FilterChain
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了
命令模式
备忘录模式
状态模式
访问者模式
[中介者模式]
解释器模式
模板方法设计模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。