【十七】常用的设计模式

目录

一、单例模式

1、饿汉式:

2、懒汉式:

3、内部类的方式:

4、为什么使用单例模式?

二、代理模式

1、静态代理

2、动态代理

①JDK动态代理

②Cglib代理

③动态代理的选用

三、工厂模式

1、简单(静态)工厂类

2、使用反射的简单工厂

3、多方法工厂(常用)

4、普通工厂(非静态方法获取对象的实例)

5、抽象工厂(在普通工厂的基础上添加多个产品)

 四、装饰模式


一、单例模式

1、饿汉式:

class Singleton{ //饿汉式

    private static Instance _instance = new Singleton();         

    privateSingleton(){}  

      

    public static Instance getInstance(){  

         return _instance;  

     }  

静态成员只会被初始化一次,不存在线程安全问题。没有实现懒加载,在主动使用类时就会创建对象。

优点:在类加载的时候创建一次实例(使用Class.forName(Singleton.class)),不会存在多个线程创建多个实例的情况

缺点:即使是这个单例没有用到也会被创建,而且在类加载的时候就创建了,内存浪费了。

适合单例占用内存比较小,在初始化时就会被用到的情况。

2、懒汉式:

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关键字调用构造上函数创建对象。但是可以使用反射调用

3、内部类的方式:

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)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。

4、为什么使用单例模式?

避免在开发程序的时候,创建出一个类的多个实例(占用空间,性能问题),所以使用单例模式,保证该类只创建一个对象,减少空间的占用;

 

二、代理模式

1、静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.

缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.(类爆炸问题)

2、动态代理

①JDK动态代理

代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。

static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
  • Class[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型(用来指定返回的代理对象的接口类型-----与目标对象的类型相同)
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
InvocationHandler: Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:指代返回的代理对象

method:指代的是我们所要调用目标对象的某个方法的Method对象

args:指代的是调用目标对象某个方法时接受的参数

注意:JDK动态代理中,目标对象一定要实现接口(实现类可以是final class),否则不能用动态代理。

②Cglib代理

它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.所以目标类不能是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很复杂,需要很多的参数,而这些参数中大部分都是固定

1、简单(静态)工厂类

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 不同的产品需要不同额外参数的时候 不支持。

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、不同的产品需要不同额外参数的时候 不支持。

3、多方法工厂(常用)

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();

    }

}

优点:可以指定具体的参数。相对于简单的静态工厂类来说要便于扩展一些,因为多工厂方法只需要增加一个方法,而静态工厂类每次需要更改同一个方法。

根据这个例子可以感受到工厂方法的魅力:方便创建 同种类型的 复杂参数 对象。

4、普通工厂(非静态方法获取对象的实例)

普通工厂的特点:产品需要抽象并且工厂也需要抽象(每个工厂提供一类产品)

                               工厂方法的好处就是更拥抱变化。当需求变化,只需要增删相应的类,不需要修改已有的类。

 

5、抽象工厂(在普通工厂的基础上添加多个产品)

前四种工厂都是单产品系的抽象工厂是多产品系的(相当于一个工厂生产多种不同类的产品——比如饮料和面)

缺点:类爆炸问题:如果同一个工厂还要添加产品,这时就需要修改抽象工厂类,然后其所有具体工厂子类都需要修改。

 四、装饰模式

装饰者模式:多组合,少继承

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

 

特点:

(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。

(2) 装饰对象包含一个真实对象的引用(reference)

 

适用性:

1、需要扩展一个类的功能,或给一个类添加附加职责。

2、需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。

 

优点:

Decorator(装饰者)模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。

缺点:

装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。

 

例:

【十七】常用的设计模式_第1张图片

 

 

你可能感兴趣的:(面试)