目录
- 设计模式的分类
- 1. 创建型模式
- 2. 结构型模式
- 3. 行为型模式
- 1. 单例模式
- 1. 单例模式的三要素
- 2. 优点
- 3. 单例的实现方式(思路:思考一下Java创建对象的几种方式?好像解题思路也不对。。。基本都是通过new出来的)
- 4. 非枚举创建的单例,存在的问题
- 5. 单例模式常见面试题(TODO)
- 2. 代理模式
设计模式的分类
1. 创建型模式
1. 单例模式(一个实例)
某个类只能有一个实例,提供一个全局的访问点
2. 工厂模式(工厂提供创建类的方法)
定义一个创建对象的接口,让子类决定实例化那个类
3. 抽象工厂模式(抽象工厂接口,要有实现具体工厂类)
定义一个抽象工厂接口,具体的实现类由实现这个抽象工厂接口的方法去实现
4. 建造者模式(xxxBuilder、是特定的类)
关键字:指挥者Director、abstract类的builder、继承抽象类的builder实现类
1. 指挥者Director
指挥者是一个类,该类需要含有Builder接口声明的变量。
1. 有个Builder对象()
2. 有个执行组装builder的方法
3. 有个获取Computer的方法(getComputer)
1. 作用
指挥者的职责是负责向用户提供具体生成器,即指挥者将请求具体生成器类来构造用户所需要的Product对象,如果所请求的具体生成器成功地构造出Product对象,指挥者就可以让该具体生产器返回所构造的Product对象
2. 抽象生成器abstract类(ComputerBuilder)
封装一个复杂对象的构建过程,并可以按步骤构造
1. protected类型的对象
这个对象是我们要创造好的产品,只是还没有进行任何赋值操作,比如Computer
2. 对外访问的方法
getComputer、buildComputer
3. 抽象方法(需要具体类实现)
各种组件的build,如buildMaster、buildScreen等等
3. 具体生成器(AppleComputerBuilder)
继承抽象生成器,并实现其中的abstract方法
5. 原型模式(实现cloneable接口、重写clone方法)
通过复制现有实例来创建新的实例,无需知道相应类的信息。
1. 实现一个接口:cloneable
2. 重写一个方法:clone()
可以实现深拷贝
2. 结构型模式
1. 适配器模式(类似转接头的功能)
将一个类的接口转换成客户希望的另一个接口。 Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作
白话文:A不能和B直接相连,A需要在构造方法里塞一个适配器,这个适配器是能和B相连的,A只需要调用适配器的方法,然后让适配器连上B,这样A就可以和B相连了
2. 桥接模式()
是将抽象部分与它的实现部分分离,使它们都可以独立地变化 又称为柄体(Handle and Body)模式或接口(interface)模式
白话文:把属性整块抽出来,变成一个类,并放到构造方法中
3. 组合模式(部分-整体的结构)
将对象组合成树形结构以表示“”部分-整体“”的层次结构
1. 抽象构件(Component)角色:
它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。
白话文:有点像链表
4. 装饰模式(加新功能)
动态的给对象添加新的功能
白话文:辅助功能,不改变原来的类属性,比如查询计算呀打印呀等等
5. 外观模式(对外提供简单接口,不用管里面有多复杂)
隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口
白话文:我给你一个简单的接口,你别管里面复杂的实现
1. 门面类 Facade
6. 享元模式(有个Map存放创建的类,有的话就不用创建了)
减少对象的创建,降低系统的内存,使效率提高。
白话文:有个享元工厂类,里面有个Map,当一个类存在Map的时候,就直接拿过来用了,不需要再重新创建
1. FlyweightFactory(享元工厂类)
享元工厂类创建并且管理享元类,享元工厂类针对享元类来进行编程,通过提供一个享元池(阿不就是Map对象嘛)来进行享元对象的管理。一般享元池设计成键值对,或者其他的存储结构来存储。当客户端进行享元对象的请求时,如果享元池中有对应的享元对象则直接返回对应的对象,否则工厂类创建对应的享元对象并保存到享元池。
2. Flyweight (享元抽象类)
一般是接口或者抽象类,定义了享元类的公共方法。这些方法可以分享内部状态的数据,也可以调用这些方法修改外部状态。
3. ConcreteFlyweight(具体享元类)
具体享元类实现了抽象享元类的方法,为享元对象开辟了内存空间来保存享元对象的内部数据,同时可以通过和单例模式结合只创建一个享元对象。
7. 代理模式
1. 静态代理
白话文:共同接口多了一个方法,目标对象和代理方法都要手动实现这个方法(方法加一个我要在两个地方都写一下)
2. 动态代理
关键字:Proxy.newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) {}
1. Proxy.newProxyInstance方法
入参分别有:
1. ClassLoader类加载器
2. interface被代理的接口
3. InvocationHandler接口
2. 实现InvocationHandler.invoke方法
在实现类中通过反射调用目标方法
动态代理的本质,就是使用反射机制实现
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
//被代理的接口
void setRent(Rent rent){
this.rent = rent;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),
(org.springframework.cglib.proxy.InvocationHandler) this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现
Object result = method.invoke(rent,args);
return result;
}
}
3. 行为型模式
1. 访问者模式
TODO
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离。
1. 抽象元素(Element)角色
声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
2. 抽象访问者(Visitor)角色
定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
2. 模板模式(特定步骤延迟到子类实现)
定义一个算法结构,而将一些步骤延迟到子类实现
1. 好处
模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
3. 策略模式(对外提供方法不变,具体策略通过构造方法传入)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。
白话文:对外提供的方法是不变的,变化的是创建这个类的构造方法传入的策略,会根据这个具体的策略去执行
1. 抽象策略角色(如Comparator接口)
这个是一个抽象的角色,通常情况下使用接口或者抽象类去实现。对比来说,就是我们的Comparator接口。
2. 策略角色实现类
包装了具体的算法和行为。对比来说,就是实现了Comparator接口的实现一组实现类。
4. 状态模式
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
TODO
5. 观察者模式(公众号推送消息给关注者)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
1. 观察者的接口(比如User)
2. 被观察者的接口(比如公众号)
当然这里可以维护一个UserList,存放订阅了这个消息的用户
3. 观察者和被观察者的多对多关系表
2. 例子
有一个微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息
6. 备忘录模式(备份,能够找到历史记录)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
白话文:多了一个备忘录List,用来存储对象的历史状态,这样的话,原对象改变数据也没关系
7. 中介者模式(A、B对话,聊天室是中介者,解耦)
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
白话文:以前是:A给B发消息,直接发送,A、B耦合;现在是:A调用聊天室的接口,聊天室给B发送消息,A、B解耦;
8. 迭代器模式(核心是:迭代器接口、服务员点菜)
提供一种方法顺序访问一个聚合对象(就是List集合)中各个元素, 而又无须暴露该对象的内部表示。
白话文:
1. 迭代器接口(2个方法)
1. 判断是否遍历结束
hasNext
2. 取得下一个元素
next
2. 例子
以菜单为例,有早餐菜单、午餐菜单,服务员只要知道有2种菜单就行了,而不需要管菜单里面具体有什么菜
菜单类会去实现迭代器接口,并提供给服务员一个获取菜单信息的接口(这个接口使用迭代器去遍历元素实现的)
9. 解释器模式(表达式接口,自定义实现用来解释的)
给定一个语言,定义它的文法的一种表示,并定义一个解释器。
1. 表达式接口
白话文:多了一个表达式接口,具体实现的逻辑可以自定义,比如是否重复、与或非逻辑判断等等
10. 命令模式
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
1. 请求者
是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
2. 命令接口类
声明执行命令的接口,拥有执行命令的抽象方法 execute()。
3. 命令实现类
是抽象命令类的具体实现类,它拥有接收者对象(Light),并通过调用接收者的功能来完成命令要执行的操作。
4. 接受者对象
执行命令的对象
11. 责任链模式(链表结构)
如果有多个对象有机会处理请求,责任链可使请求的发送者和接受者解耦,请求沿着责任链传递,直到有一个对象处理了它为止
1. 决策者抽象类(2个方法)
处理请求的方法、推送给下一个决策者的方法
1. 结构的话,其实就是链表
白话文:结构的话,其实就是链表啦
2. 还需要提供一个决策的方法
就是说什么情况下会自己处理,什么情况下会推送给下一个决策者
2. 决策者的实现类
3. 例子
购买请求决策,价格不同要由不同的级别决定:组长、部长、副部、总裁
1. 单例模式
保证一个类只有唯一的一个实例,并提供一个全局的访问点。
1. 单例模式的三要素
- 私有静态实例引用
- 私有的构造方法
- 返回静态实例的静态公有方法
白话文:1个构造方法 + 1个引用(不一定有实例)+ 1个获取单例的方法
2. 优点
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;(只有一个实例)
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。(提供了获取单例的方法)
3. 单例的实现方式(思路:思考一下Java创建对象的几种方式?好像解题思路也不对。。。基本都是通过new出来的)
1. 饿汉模式单例(在类加载时会初始化)
饿汉在类加载的时候就会初始化,所以不会有线程安全的问题,getInstance不需要有任何操作,直接拿到instance就行
1. new方式
public class SingletonDemo {
// 在类加载的时候直接new这个实例
private static SingletonDemo instance = new SingletonDemo();
private SingletonDemo(){}
public static SingletonDemo getInstance(){
return instance;
}
}
在类加载的时候直接new这个实例
2. 静态代码块
public class SingletonDemo {
private static SingletonDemo instance = null;
static{
instance = new SingletonDemo();
}
private SingletonDemo(){}
public static SingletonDemo getInstance(){
return instance;
}
}
静态代码块在类加载的时候就会执行
题外话:静态代码块相比直接new的方式,到底好在哪里?TODO
3. 枚举实例化
public enum SingletonDemo {
INSTANCE;
public SingletonDemo getInstance(){
return INSTANCE;
}
}
1. 优点:防止反射问题、防止反序列化问题、防止clone
2. 枚举反编译后的结果:
public final class SingletonDemo extends Enum<SingletonDemo> {
public static final SingletonDemo SINGLETONDEMO;
public static SingletonDemo[] values();
public static SingletonDemo valueOf(String s);
static {};
}
// 这是Enum类
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
// 名称
private final String name;
public final String name() {
return name;
}
// 序号
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
}
3. 枚举为什么不能通过反射创建实例?(要看源码Constructor的newInstance方法)
源码分析:Class类通过反射调用Constructor类的newInstance方法创建实例
public final class Constructor<T> extends Executable {
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 原因在这里:如果这个Class类是属于Enum的话,则会报异常,创建失败
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
}
如果这个Class类是属于Enum的话,则会报异常,创建失败
4. 枚举为什么可以避免序列化问题?(重写了readObject方法)
反序列化创建实例的本质是调用Object的readObject方法,而Enum类的方法一调用就会报异常
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
// 原因在这里:直接报“不能序列化枚举”异常
throw new InvalidObjectException("can't deserialize enum");
}
}
原因是:Enum重写了Object的readObject方法,当调用的时候会直接报异常
5. 枚举为什么可以避免clone问题?(重写了clone方法)
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
protected final Object clone() throws CloneNotSupportedException {
// 原因在这里:直接报“不支持Clone”异常
throw new CloneNotSupportedException();
}
}
原因是:Enum重写了Object的clone方法,当调用的时候会直接报异常
2. 懒汉模式单例(在类加载时不初始化,调用获取单例的方法时候再初始化)
在调用静态方法getInstance时会实例化,
1. 在静态方法中使用双重校验(保证只创建一个实例)
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInsatance(){
// 第一次校验,如果已经创建好实例的话,就不用去获取锁了
if (instance == null) {
// A、B两个线程同时到这里来了,A获取了锁,B在这里阻塞等待
synchronized (SingletonDemo.class) {
// 第二次校验,防止未创建实例时,A获取锁创建了实例,B之后获取锁又创建了实例
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return singletonDemo;
}
}
注意:这里用到了双重校验机制,2个IF分别有什么作用要记住,一个都不能删掉
2. 在静态方法中调用静态内部类
public class SingletonDemo {
// 静态内部类
private static class SingletonHolder{
private static final SingletonDemo instance = new SingletonDemo();
}
private SingletonDemo(){}
public static final SingletonDemo getInsatance(){
return SingletonHolder.instance;
}
}
注意:静态内部类在类加载的时候,是不会被扫描JVM到的,所以不会在类加载的时候实例化
3. 基于容器实现单例(单例注册表)
Spring创建Bean的方式(有兴趣可以深入了解下)
spring的BeanDefinition(因为BeanDefinition)通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式
public class ContainerSingleton {
private ContainerSingleton() {
}
// ioc容器本质就是一个ConcurrentHashMap(确认过了,是的)
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
// 获取实例的方法,不同的是需要有入参
public static Object getInstance(String className) {
Object instance = null;
// 第一次校验
if (!ioc.containsKey(className)) {
synchronized (ContainerSingleton.class) {
// 第二次校验
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
}
}
return ioc.get(className);
}
}
本质是采用在静态方法中使用双重校验实现,区别是存放实例的地方变了
题外话:为什么BeanFactory和ApplicationContext都是调用BeanDefinition来初始化实例的,BeanDefinition使用懒加载的方式实现,BeanFactory可以理解,但是ApplicationContext是如何在Spring容器启动的时候去创建实例的?TODO
4. 非枚举创建的单例,存在的问题
原文链接:为什么要用枚举实现单例模式(避免反射、序列化问题)
1. 可以使用反射机制调用私有构造器,创建第二个实例
解决:修改构造方法,当调用的时候直接抛异常就行
2. 序列化前后,实例发生改变
public class SerSingleton implements Serializable {
private volatile static SerSingleton uniqueInstance;
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
private SerSingleton() {
}
public static SerSingleton getInstance() {
if (uniqueInstance == null) {
synchronized (SerSingleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new SerSingleton();
}
}
}
return uniqueInstance;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerSingleton s = SerSingleton.getInstance();
s.setContent("单例序列化");
System.out.println("序列化前读取其中的内容:"+s.getContent());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
oos.writeObject(s);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SerSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
SerSingleton s1 = (SerSingleton)ois.readObject();
ois.close();
System.out.println(s+"\n"+s1);
System.out.println("序列化后读取其中的内容:"+s1.getContent());
System.out.println("序列化前后两个是否同一个:"+(s==s1));
}
}
控制台:
序列化前读取其中的内容:单例序列化
com.lxp.pattern.singleton.SerSingleton@135fbaa4
com.lxp.pattern.singleton.SerSingleton@58372a00
序列化后读取其中的内容:单例序列化
序列化前后两个是否同一个:false
任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve
因为readObject方法的存在,导致每次序列化前后的对象
解决:重写readObject方法并在方法内直接抛异常
3. 可以通过clone拷贝的方式去创建一个新的实例
解决:继承clone并在方法内直接抛异常
5. 单例模式常见面试题(TODO)
问题来源是在这里,答案是我自己总结的
原文链接:https://blog.csdn.net/androidzhaoxiaogang/article/details/6832364
1. 哪些类是单例模式的后续类?在Java中哪些类会成为单例?
这里它们将检查面试者是否有对使用单例模式有足够的使用经验。他是否熟悉单例模式的优点和缺点。
2. 你能在Java中编写单例里的getInstance()的代码?
使用双重校验或静态内部类
很多面试者都在这里失败。然而如果不能编写出这个代码,那么后续的很多问题都不能被提及。
3. 在getInstance()方法上同步有优势还是仅同步必要的块更优优势?你更喜欢哪个方式?
只锁创建实例的那块代码就行,大多数情况下都是获取已经创建的实例
这确实是一个非常好的问题,我几乎每次都会提该问题,用于检查面试者是否会考虑由于锁定带来的性能开销。因为锁定仅仅在创建实例时才有意义,然后其他时候实例仅仅是只读访问的,因此只同步必要的块的性能更优,并且是更好的选择。
4. 什么是单例模式的延迟加载或早期加载?你如何实现它?
问的就是对类加载和性能开销的理解(这个问题在【八股文】JVM篇的时候再讲)
这是和Java中类加载的载入和性能开销的理解的又一个非常好的问题。我面试过的大部分面试者对此并不熟悉,但是最好理解这个概念。
5. JDK中的单例模式的实例有哪些?(好问题)
1.JDK中的饿汉模式(JVM启动时实例化)
1. java.lang.Runtime类(JVM进程启动的,供所有线程使用)
Runtime类封装了Java运行时的环境。每一个java程序实际上都是启动了一个JVM进程,那么每个JVM进程都是对应这一个Runtime实例,此实例是由JVM为其实例化的。每个Java应用程序都有一个 Runtime类实例,使应用程序能够与其运行的环境相连接。
由于Java是单进程的,所以,在一个JVM中,Runtime的实例应该只有一个。所以应该使用单例来实现。
1. 源码如下:
package java.lang;
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
Runtime是通过饿汉模式的new方式创建实例的(实现的方法有点老了)
2. 为什么Runtime类要用饿汉模式?
Runtime很重要(封装了Java运行时的环境),让JWM去实例化,总不可能让JAVA应用程序去实例化吧
2. JDK中的懒汉模式(GUI包下)(JVM启动时不初始化)
1. java.awt.Toolkit#getDefaultToolkit()
2. java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()
3. java.awt.Desktop#getDesktop()
这三个类都是在JDK的GUI包下的,并不常使用,所以为了节省资源,使用了懒汉模式
这是个完全开放的问题,如果你了解JDK中的单例类,请共享给我。
6. 单例模式的两次检查锁是什么?
说的就是DCL双重校验机制,在懒汉模式下的
7. 你如何阻止使用clone()方法创建单例实例的另一个实例?
单例类去实现Object中的clone方法并直接抛出异常就可以了
问:枚举类默认能防止clone创建实例吗?可以的,Enum类重写了clone方法
该类型问题有时候会通过如何破坏单例或什么时候Java中的单例模式不是单例来被问及。
8. 如何阻止通过使用反射来创建单例类的另一个实例?
在私有的构造方法中抛出异常
开放的问题。在我的理解中,从构造方法中抛出异常可能是一个选项。
9. 如何阻止通过使用序列化来创建单例类的另一个实例?
实现Object的readObject方法并抛出异常
又一个非常好的问题,这需要Java中的序列化知识并需要理解如何使用它来序列化单例类。该问题是开放问题。
10. Java中的单例模式什么时候是非单例?
问的其实就是什么情况会导致多个实例,如未加锁的并发访问、反射调用构造方法、通过序列化的readObject创建实例、通过Object的clone方法(浅拷贝或深拷贝)创建实例等等(没等等了好像就这么多吧)
2. 代理模式
1. 概念
是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。
2. 要素
1. 共同接口
目标对象和代理对象需要需要实现的公共接口
为什么要实现同一个接口?
答:不实现统一接口的话代理方法有可能会不能完全实现(因为实现接口必须实现它的抽象方法)
白话文:保证一定能实现目标类所有方法的代理,而且方法名都相同,好识别
public interface CommonInterface {
public void method();
}
2. 目标对象(被代理类)
也是一个类,是要被代理的类
public class Taeget implements CommonInterface{
@Override
public void method() {
System.out.println("执行目标自己的方法");
}
}
3. 代理对象(代理类)
是一个代理类(问,可以是一个接口吗??),如Proxy类
public class TargetProxy implements CommonInterface {
Taeget taeget = null;
public TargetProxy(Taeget taeget){
this.taeget = taeget;
}
@Override
public void method() {
taeget.method();
System.out.println("如:记录日志");
}
}
3. 种类
1. 静态代理
1. 什么是静态代理?
在代理对象执行这个目标方法前后,执行添加功能,每次要在公共接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法
白话文:共同接口多了一个方法,目标对象和代理方法都要手动实现这个方法(方法加一个我要在两个地方都写一下)
1. 动态代理
1. jdk动态代理(反射、类加载器、Proxy类、InvocationHandler接口)
是使用反射技术获得类的加载器并且创建实例,根据类执行的方法在执行方法的前后发送通知。
在代理对象Proxy的新建代理实例方法中,必须要获得类的加载器、类所实现的接口、还有一个拦截方法的句柄。(什么是句柄?InvocationHandler吗,是的)
在句柄的invoke中如果不调用method.invoke则方法不会执行。在invoke前后添加通知,就是对原有类进行功能扩展了。
创建好代理对象之后,proxy可以调用接口中定义的所有方法,因为它们实现了同一个接口,并且接口的方法实现类的加载器已经被反射框架获取到了。
白话文:通过反射获取类加载器,创建代理类(这个代理类继承Proxy类、实现InvocationHandler接口)
调用目标方法的时候,代理类调用目标方法的时候,会调用InvocationHandler中的invoke方法,再去调用目标方法。(猜的)
2. cglib动态代理(继承的方式,生成一个目标对象的子类)
1. 什么是cglib(优秀的代码生成类库)
优秀的代码生成类库
2. 如何实现动态代理(继承的方式,非接口)
Cglib实现代理的方式是和目标对象使用同一个父类,无论是继承还是实现接口,都是为了代理对象能直接调用目标对象的方法。
3. 总结
无论是继承还是实现接口,都是为了代理对象能直接调用目标对象的方法(目的一样)
4. 特点
- 开闭原则(对扩展开放,对修改关闭):在不修改原有形态的基础上扩展出新的功能
- 对客户端只暴露出接口,不暴露它以下的架构(宏观特性)
5. 代理对象一般都干什么事情?
- 执行目标对象的方法:自己执行不了,需要代理对象帮忙执行一下
- 扩展目标对象的方法:自己执行了一部分,其他部分需要代理对象统一去执行