前言
老实说,面试面来面去也就这么五六种设计模式,所以打算抽出点时间详细的了解下这五种设计模式。
单例
考的最多,同时也算是最常用的一种模式了,可以有以下几种实现:
- 懒汉式:只有当需要的时候才创建对象,线程不安全
- 饿汉式:在类加载的时候直接就创建好,非常简单。
- double check:懒汉式的一种,通过两次的if判断来满足线程安全。
- 静态内部类:懒汉式的一种,利用了静态内部类线程安全的特点
- 枚举:JVM自带的一种关键字实现,可以确保线程安全。
饿汉式
1 | public class HungrySingleton { |
优点是简单,而且不会出现线程安全问题。缺点是不管你用不用,都会创建这个实例。
懒汉式(线程不安全)
1 | public class LazySingleton { |
显然假设有两个线程A和B,如果A在已经进入if判断之后时间片到了,那么就会发生两次构造器的调用了。
double check
1 | public class DoublecheckSingleton { |
这里指的注意的就是用volatile修饰的,也是面试官最喜欢问的。volatile在这里一是为了内存的可见性,二是为了防止指令重排序,导致先分配了空间但是没有初始化。
静态内部类
1 | public class StaticInnerClassSingleton { |
推荐使用,简单好写,且由JDK来进行保证单例的正确性和线程安全。但是会被反射轻松获取。
枚举
1 | public enum EnumSingleton { |
emmm 应该没有比它更简单的单例了吧?而且它线程安全。
代理模式
反射的时候用到过,spring AOP也大量使用。
如果一个对象的功能不能满足你的要求,可以用代理对象来增强。
静态代理
首先需要定义一个接口:
1 | public interface Raw { |
然后有一个原始的实现类:
1 | public class RawImpl implements Raw{ |
最后用一个类来持有这个原始的实现类,并且对其进行加强:
1 | public class RawImplProxy implements Raw { |
动态代理
代理的对象不在需要去实现接口了,但是被代理的对象仍然需要实现接口。用的是JDK的API,在内存中动态构件代理对象。
也就是,创建代理不需要你自己做了,JDK已经帮你完成了,你只需要去调用Proxy类下面的静态方法就行了。
1 | public class RawImplProxy implements Raw { |
可以看到上面的类持有两个Object对象,一个是target,也就是需要被代理的类,显然就是RawImpl;然后是proxy,proxy是用jdk的反射方法生成的一个代理对象,然后我们拿着这个代理对象来操作就行了。
cglib
cglib本身是属于动态代理的范畴,但是由于它是使用的继承类来进行增强,而不是使用接口的方法,所以单独拿出来说一说。
核心思路是一样的,都是使用现成的方法来生成对应的代理对象。假设A需要增强B,那么A只需要去实现一个cglib的接口,然后重写里面的方法就可以了..
适配器
也算是比较常用的一种设计模式了。
类适配器
通过继承一个类src,然后实现另外一个类dest的接口,就能把两者融合在一起。
对象适配器
这次不继承src,而是持有src的实例。
观察者模式
这个模式主要应用于当一个对象变化之后,其它依赖这个对象的对象能够得到通知。有点类似现在的xx主播开播了然后推送信息给你一样。或者再往前就跟RSS订阅一样。
其实用起来很简单,java有很好的抽象(从JDK9之后被废弃了),我们只需要继承并且实现相关的代码就好了:
1 | public class MyObservable extends Observable { |
我们只需要在方法中加入两行,就可以通知观察者了。同时观察者需要处理一下逻辑:
1 | public class MyObserver implements Observer { |
然后在使用的时候,只需要将观察者加入进去,然后在调用相关方法的时候就会去调用相关的观察者了:
1 | public class Main { |
实现简单而又优雅,缺点就是如果一个对象被非常多观察者所观察,那么可能需要不少代码。其次是它不能被序列化,最后是它线程不安全。
策略模式
策略模式简单来说就是一个对象持有一个接口,然后根据给接口传入不同的多态对象来进行不同的处理。最常见的应该就是Comparator了,根据不同的实现类来实现排序。
1 | public class Main { |
优点是我们只需要使用不同的”排序器”就可以实现相关的排序,而不需要去大幅度的改动源代码。
享元模式
我是一直没搞懂为什么Flyweight(轻量级)为什么会被翻译成享元,共享元数据的意思么?反正按照英文理解就很容易,就是如果有能够共享的对象,那么就直接使用。最常见的就是线程池、数据库连接池等。