目录
一、单例模式
1、饿汉式:
2、懒汉式:
3、内部类的方式:
4、为什么使用单例模式?
二、代理模式
1、静态代理
2、动态代理
①JDK动态代理
②Cglib代理
③动态代理的选用
三、工厂模式
1、简单(静态)工厂类
2、使用反射的简单工厂
3、多方法工厂(常用)
4、普通工厂(非静态方法获取对象的实例)
5、抽象工厂(在普通工厂的基础上添加多个产品)
四、装饰模式
class Singleton{ //饿汉式
private static Instance _instance = new Singleton();
privateSingleton(){}
public static Instance getInstance(){
return _instance;
}
静态成员只会被初始化一次,不存在线程安全问题。没有实现懒加载,在主动使用类时就会创建对象。
优点:在类加载的时候创建一次实例(使用Class.forName(Singleton.class)),不会存在多个线程创建多个实例的情况
缺点:即使是这个单例没有用到也会被创建,而且在类加载的时候就创建了,内存浪费了。
适合单例占用内存比较小,在初始化时就会被用到的情况。
class Singleton {//懒汉式
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {//在这里使用同步代码块而不在整个方法使用,是由于在这里使用性能高
if (instance == null) {//为什么再次判断?高并发下多个线程可能会进入第一个if块中,如果不再次判断会创建多个实例
instance = new Singleton();
}
}
}
return instance;
}
}
适合单例使用的次数少,并且创建单例消耗的资源较多,就需要按需创建的情况。
为了提高效率,不对整个方法进行加锁,只有当instance为null时,需要获取同步锁,创建一次实例。当实例被创建,则无需试图加锁。通过双重检查锁来解决线程安全问题:两次判断对象是否为空。
但是此时还会出现问题:指令重排优化的问题。所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。
这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。
因此需要加上volatile关键字,volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。
注意:单例模式的类的构造函数都是private的。
通过将构造函数声明为private可以防止程序员通过new关键字调用构造上函数创建对象。但是可以使用反射调用
public class Singleton {
private static class Holder {
private static Singleton singleton = new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return Holder.singleton;
}
}
也是利用了类加载机制,因此不存在多线程并发的问题,但是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。
上面四种出现的问题:
1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
避免在开发程序的时候,创建出一个类的多个实例(占用空间,性能问题),所以使用单例模式,保证该类只创建一个对象,减少空间的占用;
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.(类爆炸问题)
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。
static Object newProxyInstance(ClassLoader loader, Class>[] interfaces,InvocationHandler h )
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
InvocationHandler: Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy:指代返回的代理对象
method:指代的是我们所要调用目标对象的某个方法的Method对象
args:指代的是调用目标对象某个方法时接受的参数
注意:JDK动态代理中,目标对象一定要实现接口(实现类可以是final class),否则不能用动态代理。
它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.所以目标类不能是final class,方法如果是final/static 那么就不会执行目标对象额外的业务方法。
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.
2.引入功能包后,只需要生成代理对象的类继承MethodInterceptor,就可以在内存中动态构建子类
3.目标类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理
原因:
JDK代理在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
一句话总结工厂模式:方便创建 同种产品类型的 复杂参数 对象
工厂模式重点就是适用于 构建同产品类型(同一个接口 基类)的不同对象时,这些对象new很复杂,需要很多的参数,而这些参数中大部分都是固定的
public class SimpleNoodlesFactory {
public static final int TYPE_LZ = 1;//兰州拉面
public static final int TYPE_PM = 2;//泡面
public static final int TYPE_GK = 3;//干扣面
public static INoodles createNoodles(int type) {
switch (type) {
case TYPE_LZ:
return new LzNoodles();
case TYPE_PM:
return new PaoNoodles();
case TYPE_GK:
default:
return new GankouNoodles();
}
}
public static void main(String[] args) {
INoodles noodles = SimpleNoodlesFactory.createNoodles(TYPE_GK);
noodles.desc();
}
}
特点:一个具体的类,create方法为静态的所以称作为静态工厂类,使用if或者switch判断
缺点:1 扩展性差,需要修改工厂类(我想增加一种面条,除了新增一个面条产品类,还需要修改工厂类方法)
2 不同的产品需要不同额外参数的时候 不支持。
public class StaticNoodlesFactory {
/**
* 传入Class实例化面条产品类
*
* @param clz
* @param
* @return
*/
public static T createNoodles(Class clz) {
T result = null;
try {
result = (T) Class.forName(clz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
INoodles lz = StaticNoodlesFactory.createNoodles(LzNoodles.class);
lz.desc();
}
}
特点:是一个具体的类,非接口和抽象类。但它的create()方法,是利用反射机制生成对象返回,好处是增加一种产品时,不需要修改create()的代码。
缺点: 1、为了工厂而工厂,只是仅仅利用反射调用无参的构造方法(还不如直接利用反射创建)。
2、不同的产品需要不同额外参数的时候 不支持。
public class MulWayNoodlesFactory {
/**
* 模仿Executors 类
* 生产泡面
*
* @return
*/
public static INoodles createPm() {
return new PaoNoodles();
}
/**
* 模仿Executors 类
* 生产兰州拉面
*
* @return
*/
public static INoodles createLz() {
return new LzNoodles();
}
/**
* 模仿Executors 类
* 生产干扣面
*
* @return
*/
public static INoodles createGk() {
return new GankouNoodles();
}
}
优点:可以指定具体的参数。相对于简单的静态工厂类来说要便于扩展一些,因为多工厂方法只需要增加一个方法,而静态工厂类每次需要更改同一个方法。
根据这个例子可以感受到工厂方法的魅力:方便创建 同种类型的 复杂参数 对象。
普通工厂的特点:产品需要抽象并且工厂也需要抽象(每个工厂提供一类产品)。
工厂方法的好处就是更拥抱变化。当需求变化,只需要增删相应的类,不需要修改已有的类。
前四种工厂都是单产品系的,抽象工厂是多产品系的(相当于一个工厂生产多种不同类的产品——比如饮料和面)
缺点:类爆炸问题:如果同一个工厂还要添加产品,这时就需要修改抽象工厂类,然后其所有具体工厂子类都需要修改。
装饰者模式:多组合,少继承
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
特点:
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的引用(reference)
适用性:
1、需要扩展一个类的功能,或给一个类添加附加职责。
2、需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
优点:
Decorator(装饰者)模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
缺点:
装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
例: