什么是设计模式?
前辈们,在长期开发中为了解决某种重复出现的问题,经过长期的总结,代码结构优化,最终确定一套解决办法。
为什么学习设计模式?
对程序设是有帮助的,提高代码额可重用性,可扩展性,可靠性,可维护性。提高程序员编码设计模式,设计出标准化程序。
设计模式有哪些?
创建型模式:如何创建对象,单例,工厂模式
结构化模式:类与类如何组合,组织出更大的结构,代理模式
行为化模式:类与类如何协作
常用的设计模式
单例模式
在一个程序中,一个类只能创建一个对象。
实现:
1. 构造方法私有化,在其他类中就不能 new 对象
2. 在单例类中,向外界提供一个公共的静态方法获得单例对象,在类的内部就可以方便控制。
分为:
饿汉式单例:在类中关联自己,为静态,当类加载时,创建单例对象,调用方法时,直接返回即可,类只加载一次。
饿汉式单例/急切式单例 不存在线程安全问题,因为类加载的时候,对象已经创建,仅且创建一次。
public class SingleTon {private static SingleTon singleTon = new SingleTon();private SingleTon1(){}public static SingleTon getSingleTon() {return singleTon;}}
懒汉式单例:在类加载的时候不着急创建,第一次调用时创建对象,会存在线程安全问题。
public class SingleTon {private static SingleTon singleTon = null;private SingleTon(){}public static SingleTon getSingleTon() {if(singleTon == null){singleTon = new SingleTon();}return singleTon;}}
第一种:加入synchronized 关键字
加入synchronized 不建议使用,执行的效率低
public class SingleTon {private static SingleTon singleTon = null;private SingleTon(){}public static synchronized SingleTon getSingleTon() {if(singleTon == null){singleTon = new SingleTon();}return singleTon;}}
第二种:双重检索+volatile
双重检索
双重检索:有多个线程进入后,会先判断是否为空,为空则进入第一层,当多个线程进入第一层后后,synchronized修饰的代码块,只允许一个线程进入第二层,为空,则创建一个SingleTon对象,否则,出第二层。当下一次线程进入后,singleTon为空,则重复前面操作,否则就直接返回singleTon变量。
public class SingleTon {private volatile static SingleTon singleTon = null;private SingleTon(){}public static SingleTon getSingleTon() {if(singleTon == null){synchronized(SingleTon.class){if(singleTon == null){singleTon = new SingleTon();}}}return singleTon;}
}
volatile关键字
原因:使用volatile关键字修饰可以保证可见性,有序性。 在创建一个SingleTon对象,并给引用对象赋值这个过程中底层拆分为三条指令。
第一步,new SingleTon() 的时候在内存中申请一份空间,这一步是不会发生重排的,
第二步,调用构造方法初始化对象。
第三步,将对象地址赋给引用变量。
但是在第二步和第三步进行的过程中,会发生重排,有可能将第三步重排到第二步执行, 这时候会造成引用变量执行的是一个半成品的对象
编译后的汇编指令码:
正常执行顺序 0 new #3 //new 申请内存空间
3 dup
4 invokespecial #4 : ()V> //调用构造方法
7 astore_1 //将对象地址赋给引用变量
8 return
线程 1 开始执行,先执行 new,在内存中申请内存空间
此时指令可能发生重排序,先把半成品对象引用地址赋给引用变量
线程 1 暂停执行,线程 2 进入到 cpu 执行,引用变量 t 不为空,指向的是半成品对象
原因:使用volatile关键字修饰可以保证可见性,有序性
在创建一个SingleTon对象,并给引用对象赋值这个过程中底层拆分为三条指令
第一步,new SingleTon() 的时候在内存中申请一份空间,这一步是不会发生重排的,
第二步,调用构造方法初始化对象
第三步,将对象地址赋给引用变量。
但是在第二步和第三步进行的过程中,会发生重排,有可能将第三步重排到第二步执行, 这时候会造成引用变量执行的是一个半成品的对象
工厂模式
创建型模型,批量创建对象,创建对象与使用对象相分离。
简单工厂模式:创建的对象不多,只需要一个工厂类就可以完成。在简单工厂模式中创建实例的方法为静态方法。
角色:
简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。创建类的方法可以被外界直接调用,创建所需产品对象。
抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
具体产品(ConcreteProduct):是简单工厂模式的创建目标
代理模式
场景:AOP的底层实现就是使用动态代理
代理模式设计思想
想完成某件事情,不能/不想直接去访问目标,可以通过中介来帮助你联系完成某件事情。
例如:找房子,不想直接去找房东,可以通过中介来帮助自己完成找房子这件事
代理优点:可以保护目标对象,对访问者提供额外操作,将客户端与目标对象分离,一定程度上降低了系统的耦合度。
静态代理
实际中很少使用静态代理,因为其代理类实现的接口必须与目标类实现接口一致扩展起来就比较麻烦。代理类必须实现与目标类相同的接口(目标类是必须要实现接口的),这样扩展性降低了。
动态代理
代理类不需要实现与目标类相同的接口,这样就可以代理任意的目标类,但是是有要求的,目标类必须实现接口的此种方法。
核心就是动态性。
怎样实现动态性?
通过java的反射机制实现,动态获取目标类信息,动态生成代理对象。目标类也是必须要实现一个接口,而代理类不需要实现与目标类相同的接口,实现invocationHandler接口。
代理类不需要实现与目标类相同的接口,这样就可以代理任意的目标类。但是是有要求的,目标类必需实现接口,此种方式是动态代理的实现方式之一:
jdk代理 是一种纯反射机制实现(动态获取目标类接口方法)
步骤:
- 编写一个委托类的接口,即静态代理的(UserDao)
- 实现一个真正的委托类,即静态代理的(UserDaoImpl)
- 创建一个动态代理类,实现InvocationHandler 接口,并重写改invoke方法(Dtproxy)
- 在测试类中,生成动态代理的对象。(Test)
总结:相比于静态代理,减少了业务的工作量,降低了系统的耦合度。但是必须使用interface代理,注定有一个共同的父类叫Proxy,即目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
代码实现
代理类:
public class Dtproxy implements InvocationHandler {Object object;//真实对象,接收任何的目标类对象public Dtproxy(Object object) {this.object = object;}/*在代理类中调用目标类中的具体方法,动态的将代理动态对象,目标类中要调用的方法,及方法中的参数传递过来Method method 就是动态获取的真正要执行的方法*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("之前开启事务");method.invoke(object);System.out.println("之后提交事务");return proxy;}}
UserDao接口:
public interface UserDao {void saveUser();
}
UserDaoImpl:
public class UserDaoImpl implements UserDao{@Overridepublic void saveUser() {System.out.println("普通用户保存");}
}
VipUserDaoImpl:
public class VipUserDaoImpl implements UserDao {@Overridepublic void saveUser() {System.out.println("vip用户保存");}
}
Test测试
public class Test {public static void main(String[] args) {VipUserDaoImpl vip = new VipUserDaoImpl();InvocationHandler dtproxy = new Dtproxy(vip);//自己创建的代理类对象//这才是真正的创建动态代理对象 //获取目标类所实现的接口UserDao userDao = (UserDao) Proxy.newProxyInstance(Dtproxy.class.getClassLoader(),VipUserDaoImpl.class.getInterfaces(),dtproxy);userDao.saveUser();//使用代理对象调用接口中的方法,获取当前调用的方法,最终调用invoke方法 }
}
执行结果:
Cglib代理
首先目标类不需要实现接口,采用动态字节码技术,可以为目标类动态的生成父类,采用方法拦截技术实现。
步骤:
- 引入chlib的jar包
- 在内存中动态的创建子类
- 代理类不能用final,否则报错
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
CGLibProxy类
/** 动态代理类*/
public class CGLibProxy implements MethodInterceptor {private Enhancer enhancer = new Enhancer();public Object getProxy(Class<?> clazz){ enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } /** 拦截所有目标类方法的调用 * 参数: * obj 目标实例对象 * method 目标方法的反射对象 * args 方法的参数 * proxy 代理类的实例 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {//代理类调用父类的方法 System.out.println("开始事务"); Object obj1 = proxy.invokeSuper(obj, args); System.out.println("关闭事务"); return obj1; }
}
public class UserDaoImpl{public void save() {System.out.println("UserDaoImpl:save()");}}
public class Test {public static void main(String[] args) {CGLibProxy proxy = new CGLibProxy(); UserDaoImpl userDaoImpl = (UserDaoImpl) proxy.getProxy(UserDaoImpl.class);userDaoImpl.save();}
}
Cglib和jdk的优缺点
- 使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
- Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型(采用动态创建子类的方法,对final修饰的方法无法进行代理)。
- 从执行效率上看,Cglib动态代理效率较高。
对象克隆
为什么要克隆?
因为new 出来的对象的属性还是初始化的值,而需要新的克隆的对象可能是已经修改过的对象,这是需要新的对象来保存当前对象的状态,而我们采用new 的方法,麻烦并且浪费空间,效率低,所以采用clone(),来解决,因为底层调用native方法,效率高。
为什么需要复制?
场景:
- 接收前端数据
- dao封装数据
- 后端向前端响应数据
引用复制:把一个对象进行复制,复制出一个新的对象。
浅克隆和深克隆的区别
是否支持引用类型的成员变量的复制
浅克隆
在浅克隆中,当对象被复制是只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
实现:
- 在 Java 语言中,通过覆盖 Object 类的 clone()方法可以实现浅克隆。
- 在 spring 框架中提供 BeanUtils.copyProperties(source,target);
深克隆
在深克隆中,除了对象本身被复制外,对象所包含的所有的成员变量也被复制。
实现:
- 通过覆盖 Object 类的 clone()方法 实现
- 可以通过序列化(Serialization)等方式来实现
序列化实现深克隆:
序列化就是将对象写到流的过程,写到流的对象是对原有对象的一个拷贝,而原对象仍然存在内存中,通过序列化实现的拷贝不仅可以复制对象本身,还可以复制其引用的成员对象,所以,可以用过序列化将对象写到一个流中,再从流中将其读出来,实现深克隆。
解决多层克隆问题
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用 clone 方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。