设计模式大汇总

170 阅读21分钟

软件设计七大原则

  1. 开闭原则:对于扩展-开放,对于修改-闭合
  2. 单一职责:类功能的单一性,高内聚低耦合
  3. 依赖倒置:各类,模块之间互不影响。

  1. 接口隔离:接口职责单一性
  2. 迪米特原则:最少知道原则,也就是说一个对象对其他对象知道的越少越好,一个类对他依赖的类知道的越少越好,到类与类之间尽量解耦,不属于当前类负责的事情不要做
  3. 里氏替换:子类可以扩展不可修改父类方法。换种说法,子类的入参要是父类入参的子类;子类的出参要是父类出参的父类
  4. 合成复用:尽量使用对象组合,不使用继承

另外两条通用原则:

  1. 找出应用中需要变化的部分,把他们独立出来,不要和那些需要变化的混在一起
  2. 针对接口编程而不是针对实现

设计模式对比和总结

设计模式含义
单例整个系统中只有一个实例
工厂封装创建一个对象的一系列步骤
策略结果是相同的,只不过选择不同的实现过程
委派分发调度一个过程
模板方法方法执行不变,只是有一部分实现过程留给子类
装饰者一个套一个,is a 的关系
代理对象增强, has a
适配器不同接口的容错方式

工厂模式

  • 定义:封装创建一个对象的一系列步骤
  • 优点:创建对象代码可复用;不需要知道复杂对象的创建过程

如:slf4j 使用简单工厂模式

public class SimpleFactoryTest {  
    private Logger log = LoggerFactory.getLogger(SimpleFactoryTest.class);  
}  

准备一个接口 ICourse 和两个实现类 JavaCourse / PythonCourse

public interface ICourse {  
    void learn();  
}  
  
public class JavaCourse implements ICourse {  
    @Override  
    public void learn() {  
        System.out.println("创建了Java");  
    }  
}  
  
public class PythonCourse implements ICourse {  
    @Override  
    public void learn() {  
        System.out.println("创建了Python");  
    }  
}  

简单工厂模式 

  • 工厂类创建的对象较少
  • 调用者只需要传入工厂类的参数,对于如何创建对象不需要关心
  • 用于封装一个对象的创建过程
public class SimpleFactory {  
  
    /**  
     * 传如参数创建对象, 不需要关心过程  
     * @param clazz  
     * @return  
     */  
    @Nullable  
    public static ICourse getCourse(Class<? extends ICourse> clazz) {  
        if (Objects.nonNull(clazz)) {  
            try {  
                return clazz.newInstance();  
            } catch (InstantiationException | IllegalAccessException e) {  
                e.printStackTrace();  
            }  
        }  
        return null;  
    }  
      
    public static void main(String[] args) {  
        ICourse javaCourse = SimpleFactory.getCourse(JavaCourse.class);  
        if (Objects.nonNull(javaCourse)) {  
            javaCourse.learn();  
        }  
        ICourse pythonCourse = SimpleFactory.getCourse(PythonCourse.class);  
        if (Objects.nonNull(pythonCourse)) {  
            pythonCourse.learn();  
        }  
    }  
  
}  

工厂方法模式

  • 定义一个创建对象的接口,但让实现这个接口的类决定实例化那个方法
  • 工厂方法让类的实例化推迟到子类中
  • 用于一个对象的创建过程有不同的实现方式
public interface ICourseFactory {  
    ICourse create();  
}  
  
public class JavaCourseFactory implements ICourseFactory {  
    @Override  
    public ICourse create() {  
        return new JavaCourse();  
    }  
}  
  
public class PythonCourseFactory implements ICourseFactory {  
    @Override  
    public ICourse create() {  
        return new PythonCourse();  
    }  
}  
  
public class FactoryMethodTest {  
    public static void main(String[] args) {  
        new JavaCourseFactory().create().learn();  
        new PythonCourseFactory().create().learn();  
    }  
}  

抽象工厂

  • 提供一个创建一系列相关或相互依赖对象的借口,无需指定他们具体的类
  • 用于定义一个对象复杂的创建步骤
public interface ICourseFactory {  
    ICourse createCourse();  
    INote createNote();  
    ISource createSource();  
}  
  
public class JavaCourseFactory implements ICourseFactory {  
    @Override  
    public ICourse createCourse() { return null; }  
    @Override  
    public INote createNote() { return null; }  
    @Override  
    public ISource createSource() { return null; }  
}  
  
public class AbstractFactoryTest {  
    public static void main(String[] args) {  
        JavaCourseFactory factory = new JavaCourseFactory();  
        factory.createCourse();  
        factory.createNote();  
        factory.createSource();  
    }  
}  

单例模式

  • 定义:应用中只有该类的一个实例
  • 优点:减少内存占用;可以控制对该对象的全局访问点
  • 缺点:扩展困难,单例对象要扩展必须修改代码

如 Spring 中的:

  • ServlectContext
  • ServlectConfig
  • ApplicationContext
  • DBPool

饿汉式单例

  • 私有化构造方法
  • 提前创建对象

创建对象的时机分为:

  • 在单例类首次加载时就创建对象
public class HungrySingleton {  
  
    private static HungrySingleton instance = new HungrySingleton();  
  
    private HungrySingleton() {  
    }  
  
    public static HungrySingleton getInstance(){  
        return instance;  
    }  
}  
  • 在静态初始化代码块中创建实例
public class HungrySingletonStatic {  
  
    private static HungrySingletonStatic instance;  
  
    static {  
        instance = new HungrySingletonStatic();  
    }  
  
    private HungrySingletonStatic() {  
    }  
  
    public static HungrySingletonStatic getInstance() {  
        return instance;  
    }  
}  

饿汉式单例在对象没有被使用时候就创建对象,会导致空间浪费,懒汉式可以避免

懒汉式单例

  • 私有化构造方法
  • 在使用对象时创建

根据对象创建方式可分为:

  • 普通懒汉式(线程不安全)
public class LazySingleton {  
    private LazySingleton() {}  
  
    private static LazySingleton instance;  
  
    synchronized public static LazySingleton getInstance() {  
        if (null == instance) {  
            instance = new LazySingleton();  
        }  
        return instance;  
    }  
}

上面的写法存在线程安全问题,如果在线程 A 判断为空之后,创建对象之前,线程 B 也进行了 null 判断,则两个线程都会创建对象,以下创建方式可以避免线程安全问题

  • Double Check
    /**  
     * Double Check 方式  
     * @return  
     */  
    public static LazySingleton getInstanceDoubleCheck() {  
        if (null == instance) {  
            synchronized (LazySingleton.class) {  
                if (null == instance) {  
                    instance = new LazySingleton();  
                }  
            }  
        }  
        return instance;  
    }  
  • volatile
public class LazySingleton {  
    private LazySingleton() {  
    }  
  
    private static volatile LazySingleton instance;  
      
    public static LazySingleton getInstanceVolatile() {  
        if (null == instance) {  
            synchronized (LazySingleton.class) {  
                //成员变量加上 volatile 关键字 防止指令重排序  
                instance = new LazySingleton();  
            }  
        }  
        return instance;  
    }  
}
  • 内部类方式

关于内部类的加载顺序请参考:一探 Java 类加载顺序 - 掘金 (juejin.cn)

public class LazyInnerSingleton implements Serializable {  
    private LazyInnerSingleton() {}  
  
    public static LazyInnerSingleton getInstance() {  
        return Inner.INSTANCE;  
    }  
  
    private static class Inner {  
        private static final LazyInnerSingleton INSTANCE = new LazyInnerSingleton();  
    }  
}

线程安全导致单例被破坏的问题虽然解决了,但反射也可能导致单例被破坏,如下操作:

    public static void main(String[] args) throws Exception {  
        Class<LazyInnerSingleton> clazz = LazyInnerSingleton.class;  
        Constructor<LazyInnerSingleton> con = clazz.getDeclaredConstructor(null);  
        con.setAccessible(true);  
        LazyInnerSingleton lazyInnerSingleton = con.newInstance();  
        System.out.println(lazyInnerSingleton);  
        //org.nyy.gperstudy.singleton.lazy.LazyInnerSingleton@61bbe9ba  
        System.out.println(LazyInnerSingleton.getInstance());  
        //org.nyy.gperstudy.singleton.lazy.LazyInnerSingleton@610455d6  
    }  

反射导致对象被再次实例化,需要对已经私有化的构造器作如下操作:

    private LazyInnerSingleton() {  
        if (null != Inner.INSTANCE) {  
            throw new RuntimeException("不允许创建多个实例!");  
        }  
    }  

再次反射创建:

image.png

反射破坏单例被解决了,但序列化也可以破坏单例,如下

public static void main(String[] args) throws RuntimeException {  
        LazyInnerSingleton instance = LazyInnerSingleton.getInstance();  
        LazyInnerSingleton readInstance;  
        try (  
                FileOutputStream fos = new FileOutputStream("LazyInnerSingleton.obj");  
                ObjectOutputStream oos = new ObjectOutputStream(fos);  
                FileInputStream fis = new FileInputStream("LazyInnerSingleton.obj");  
                ObjectInputStream ois = new ObjectInputStream(fis);  
        ) {  
            oos.writeObject(instance);  
            oos.flush();  
  
            readInstance = (LazyInnerSingleton) ois.readObject();  
  
            System.out.println(readInstance == instance);  
            //false  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  

原理分析:

查看 ObjectInputStream 的 readObject() 方法

public class ObjectInputStream  
    extends InputStream implements ObjectInput, ObjectStreamConstants  
{  
...  
    public final Object readObject() throws IOException, ClassNotFoundException{  
        ...  
        try {  
            Object obj = readObject0(false);  
            ...  
            return obj;  
        } finally {  
            ...  
        }  
    }  
    private Object readObject0(boolean unshared) throws IOException {  
        ...  
        byte tc;  
        while ((tc = bin.peekByte()) == TC_RESET) {  
            bin.readByte();  
            handleReset();  
        }  
...  
        try {  
            switch (tc) {  
                ...  
                case TC_OBJECT:  
                    return checkResolve(readOrdinaryObject(unshared));  
...  
            }  
        } finally {  
            ...  
        }  
    }  
    private Object readOrdinaryObject(boolean unshared) throws IOException {  
        ...  
        Object obj;  
        try {  
         // 无参构造器初始化  
            obj = desc.isInstantiable() ? desc.newInstance() : null;  
        } catch (Exception ex) {  
            ...  
        }  
...  
        // 如果有 readResolve 方法会调用该方法覆盖之前创建的对象  
        if (obj != null &&  
            handles.lookupException(passHandle) == null &&  
            desc.hasReadResolveMethod())  
        {  
            Object rep = desc.invokeReadResolve(obj);  
            if (unshared && rep.getClass().isArray()) {  
                rep = cloneArray(rep);  
            }  
            if (rep != obj) {  
                // Filter the replacement object  
                if (rep != null) {  
                    if (rep.getClass().isArray()) {  
                        filterCheck(rep.getClass(), Array.getLength(rep));  
                    } else {  
                        filterCheck(rep.getClass(), -1);  
                    }  
                }  
                handles.setObject(passHandle, obj = rep);  
            }  
        }  
  
        return obj;  
    }  
}  

readResolve 方法在构造方法中赋值

public class ObjectStreamClass implements Serializable {  
    ...  
    private ObjectStreamClass(final Class<?> cl) {  
        ...  
readResolveMethod = getInheritableMethod(cl, "readResolve"null, Object.class);  
        ...  
    }  
    private static Method getInheritableMethod(Class<?> cl, String name,  
                                               Class<?>[] argTypes,  
                                               Class<?> returnType){  
     ...  
    }  
}  

结论就是,给对象覆盖一个无参的readresolve方法,返回对象的单例即可,如下

    private Object readResolve() {  
        return Inner.INSTANCE;  
    }  

上述为了解决普通懒汉式单例,我们添加了各种补丁,那有没有更简便的方法呢?答案是枚举式单例

枚举单例

属于饿汉式

public enum EnumSingleton {  
    /**  
     * 实例  
     */  
    INSTANCE;  
  
    public static String str = "str";  
  
    public static EnumSingleton getInstance() {  
        return INSTANCE;  
    }  
}  

可以避免序列化破坏单例,是因为

public class ObjectInputStream  
    extends InputStream implements ObjectInput, ObjectStreamConstants  
{  
...  
    private Object readObject0(boolean unshared) throws IOException {  
        ...  
        byte tc;  
        while ((tc = bin.peekByte()) == TC_RESET) {  
            bin.readByte();  
            handleReset();  
        }  
...  
        try {  
            switch (tc) {  
                ...  
                case TC_ENUM:  
                    return checkResolve(readEnum(unshared));  
...  
            }  
        } finally {  
            ...  
        }  
    }  
    private Enum<?> readEnum(boolean unshared) throws IOException {  
        ...  
        Class<?> cl = desc.forClass();  
        if (cl != null) {  
            try {  
                @SuppressWarnings("unchecked")  
                // 使用 valueOf 获取枚举  
                Enum<?> en = Enum.valueOf((Class)cl, name);  
                result = en;  
            } catch (IllegalArgumentException ex) {  
                ...  
            }  
            ...  
        }  
...  
        return result;  
    }  

枚举不能被 new 创建出来,所以,从 JDK 层面,保证了枚举不能被 new,不被序列化破坏

容器式单例

对象方便管理,属于懒加载,存在线程安全问题,加 synchronized 解决

public class ContainerSingleton {  
  
    private ContainerSingleton() {  
    }  
  
    private static final Map<String, Object> map = new ConcurrentHashMap<>();  
  
    public static Object getBean(String className) {  
        synchronized (map) {  
            if (!map.containsKey(className)) {  
                Object obj = null;  
                try {  
                    Class<?> aClass = Class.forName(className);  
                    obj = aClass.newInstance();  
                    map.put(className, obj);  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
                return obj;  
  
            }  
            return map.get(className);  
        }  
    }  
}  

ThreadLocal 单例

天生线程安全

public class ThreadLocalSingleton {  
  
    private ThreadLocalSingleton() {  
    }  
  
    public static final ThreadLocal<ThreadLocalSingleton> LOCAL =  
            ThreadLocal.withInitial(ThreadLocalSingleton::new);  
}  

ORM 框架中数据源使用的是 ThreadLocal 变量实现多数据源动态切换

image.png

代理模式

  • 定义:属于结构性设计模式,为其他对象提供一种代理,控制这个对象的访问,保护目标对象或者对对象进行增强
  • 优点:代理模式能将代理对象与真实被调用的目标对象分离;一定程度上降低了系统的耦合度,扩展性好;可以起到保护目标对象的作用;可以对目标对象的功能增强
  • 缺点:会造成系统设计中类的数量增加;在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;增加了系统的复杂度

如 Spring 中的 AOP,动态数据源

静态代理和动态代理的本质区别:

  • 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则
  • 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成, 无需修改代理类的代码。

静态代理

public interface Person {  
    void findLover();  
}  
public class Son implements Person {  
    @Override  
    public void findLover() {  
        System.out.println("儿子找对象");  
    }  
}  
public class Father {  
    private Son son;  
    public Father(Son son){  
        this.son = son;  
    }  
  
    void findLover(){  
        System.out.println("父亲帮儿子找对象");  
        if (Objects.nonNull(this.son)){  
            this.son.findLover();  
        }  
    }  
}
public class StaticProxyTest {  
    public static void main(String[] args) {  
        //最简单的静态代理  
        Son son = new Son();  
        Father father = new Father(son);  
        father.findLover();  
    }  
}
父亲帮儿子找对象  
儿子找对象

应用:动态数据源

Service 和 Dao

public interface IOrderService {  
    void insert(Order order);  
}  
public class OrderService implements IOrderService {  
    private OrderMapper mapper = new OrderMapper();  
  
    @Override  
    public void insert(Order order) {  
        mapper.insert(order);  
    }  
}  
public class OrderMapper {  
    void insert(Order order){  
        String source = DynamicDataSourceEntity.get();  
        System.out.printf("插入数据:{%s},数据源:{%s} \n", order.getYear(), source);  
    }  
}  
public class Order {  
    private Long id;  
    private String year;  
    // GETTER / SETTER  
}  

新建 OrderServiceProxy 代理 OrderService 实现切换数据源

public class OrderServiceProxy implements IOrderService {  
  
    private OrderService orderService;  
    public OrderServiceProxy(OrderService orderService){  
        this.orderService = orderService;  
    }  
  
    @Override  
    public void insert(Order order) {  
        String year = order.getYear();  
        DynamicDataSourceEntity.set(year);  
        orderService.insert(order);  
        DynamicDataSourceEntity.reset();  
    }  
}  

切换数据源的工具    

public class DynamicDataSourceEntity {  
    private static final ThreadLocal<String> local = new ThreadLocal<>();  
    private DynamicDataSourceEntity() {  
    }  
  
    public static String get() {  
        return local.get();  
    }  
  
    public static void set(String year) {  
        System.out.println("设置数据源: " + year);  
        local.set(year + "_year");  
    }  
  
    public static void reset() {  
        System.out.println("重置数据源");  
        local.set(null);  
    }  
}  

测试

public class DbRouteTest {  
    public static void main(String[] args) {  
        Order order2020 = new Order();  
        order2020.setYear("2020");  
        Order order2021 = new Order();  
        order2021.setYear("2021");  
  
        IOrderService service = new OrderServiceProxy(new OrderService());  
        service.insert(order2020);  
        service.insert(order2021);  
    }  
}
设置数据源: 2020  
插入数据:{2020},数据源:{2020_year}   
重置数据源  
设置数据源: 2021  
插入数据:{2021},数据源:{2021_year}   
重置数据源

JDK 动态代理

JDK 提供了相关的 API 可以创建接口的代理对象

public interface InvocationHandler {  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
}  
public class Proxy implements java.io.Serializable {  
    @CallerSensitive  
    public static Object newProxyInstance(ClassLoader loader,  
                                          Class<?>[] interfaces,  
                                          InvocationHandler h) {  
     ...  
    }  
    ...  
}  

应用:媒婆找对象

Person 接口和 Girl / Son

public interface Person {  
    void findLover();  
}  
public class Girl implements Person {  
    @Override  
    public void findLover() {  
        System.out.println("女孩找对象");  
    }  
}  
public class Son implements Person {  
    @Override  
    public void findLover() {  
        System.out.println("儿子找对象");  
    }  
}  

实现 InvocationHandler 创建代理类

public class Meipo implements InvocationHandler {  
    private Person target;  
    public Object getInstance(Person person) {  
        this.target = person;  
        Class<? extends Person> aClass = person.getClass();  
        return Proxy.newProxyInstance(aClass.getClassLoader(),  
                aClass.getInterfaces(), this);  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        /*  
        proxy 是生成的代理类, 因为所有方法都被代理了, 如果在这里调用, 会出现死循环  
         */  
        before();  
        Object invoke = method.invoke(this.target, args);  
        after();  
        return invoke;  
    }  
  
    private void after() {  
        System.out.println("媒婆找对象, 代理方法之后增强");  
    }  
  
    private void before() {  
        System.out.println("媒婆找对象, 代理方法之前增强");  
    }  
}  

测试类

public class JDKProxyTest {  
    public static void main(String[] args) {  
        Person girl = (Person) new Meipo().getInstance(new Girl());  
        girl.findLover();  
        Person son = (Person) new Meipo().getInstance(new Son());  
        son.findLover();  
    }  
}
媒婆找对象, 代理方法之前增强  
女孩找对象  
媒婆找对象, 代理方法之后增强  
媒婆找对象, 代理方法之前增强  
儿子找对象  
媒婆找对象, 代理方法之后增强  

手动实现 JDK 的动态代理

用 Java 生成 Java 代码,使用 GPInvovationHandler 代替 JDK 的 InvovationHandler,生成的代理类在运行过程中实际调用了 GPInvovationHandler 实现类的 invoke 方法

public interface GPInvocationHandler {  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable;  
}  

使用 GPProxy 代替 JDK 的 Proxy,生成代理类,将代码输出到磁盘,并使用自定义的 GPClassLoader 加载

public class GPProxy {  
    public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPMeipo gpMeipo) {  
        try {  
            // 生成的源码  
            String src = genSrc(interfaces);  
  
            // 输出到磁盘  
            String path = GPProxy.class.getResource("").getPath();  
            File f = new File(path + "$Proxy0.java");  
            FileWriter fileWriter = new FileWriter(f);  
            fileWriter.write(src);  
            fileWriter.flush();  
            fileWriter.close();  
  
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();  
            StandardJavaFileManager manager = compiler.getStandardFileManager(nullnull,  
                    null);  
            Iterable<? extends JavaFileObject> javaFileObjects = manager.getJavaFileObjects(f);  
  
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null,  
                    nullnull, javaFileObjects);  
            task.call();  
  
            manager.close();  
  
            Class proxyClass = classLoader.findClass("$Proxy0");  
            Constructor constructor = proxyClass.getConstructor(GPInvocationHandler.class);  
            return constructor.newInstance(gpMeipo);  
        } catch (Throwable e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    private static String genSrc(Class<?>[] interfaces) {  
        StringBuilder src = new StringBuilder();  
        src.append("package org.nyy.gperstudy.gp;\n")  
                .append("import org.nyy.gperstudy.static0.Person;\n")  
                .append("import java.lang.reflect.*;\n")  
                .append("public class $Proxy0 implements ").append(interfaces[0].getName()).append(" {\n")  
                .append("private GPInvocationHandler h;\n")  
                .append("public $Proxy0(GPInvocationHandler h){\n")  
                .append("this.h=h;\n")  
                .append("}\n");  
  
  
        for (Method method : interfaces[0].getMethods()) {  
            src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("(){\n")  
                    .append("try{\n")  
                    .append("Method m = ").append(interfaces[0].getName()).append(".class.getMethod(\"")  
                                                            .append(method.getName()).append("\", new Class[]{});\n")  
                    .append("this.h.invoke(this, m, null);\n")  
                    .append("} catch (Throwable e) {\n")  
                    .append("e.printStackTrace();\n")  
                    .append("}\n")  
                    .append("}\n");  
        }  
  
        src.append("}\n");  
        return src.toString();  
    }  
  
}  

GPClassLoader 主要是将上文生成的代理类加载到内存,然后使用 ClassLoader#findClass(String) 拿到字节码对象

public class GPClassLoader extends ClassLoader {  
  
    private File classPathFile;  
    public GPClassLoader() {  
        String classPath = GPClassLoader.class.getResource("").getPath();  
        this.classPathFile = new File(classPath);  
    }  
  
    @Override  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        String className = GPClassLoader.class.getPackage().getName() + "." + name;  
        if (null != classPathFile) {  
            File classFile = new File(classPathFile, name + ".class");  
            if (classFile.exists()) {  
                try (  
                        FileInputStream in = new FileInputStream(classFile);  
                        ByteArrayOutputStream out = new ByteArrayOutputStream();  
                ) {  
                    byte[] bytes = new byte[1024];  
                    int len;  
                    while ((len = in.read(bytes)) != -1){  
                        out.write(bytes, 0, len);  
                    }  
                    return defineClass(className, out.toByteArray(), 0, out.size());  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
        return super.findClass(name);  
    }  
}  

新建 GPMeipo 实现自定义的 GPInvocationHandler

public class GPMeipo implements GPInvocationHandler {  
    private Person target;  
    public Object getInstance(Person person) {  
        this.target = person;  
        Class<? extends Person> aClass = person.getClass();  
        return GPProxy.newProxyInstance(new GPClassLoader(), aClass.getInterfaces(), this);  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        before();  
        Object invoke = method.invoke(this.target, args);  
        after();  
        return invoke;  
    }  
  
    private void after() {  
        System.out.println("GP媒婆找对象, 代理方法之后增强");  
    }  
  
    private void before() {  
        System.out.println("GP媒婆找对象, 代理方法之前增强");  
    }  
}  

测试类

public class GPProxyTest {  
    public static void main(String[] args) {  
        Person girl = (Person) new GPMeipo().getInstance(new Girl());  
        girl.findLover();  
        Person son = (Person) new GPMeipo().getInstance(new Son());  
        son.findLover();  
    }  
}
媒婆找对象, 代理方法之前增强  
女孩找对象  
媒婆找对象, 代理方法之后增强  
媒婆找对象, 代理方法之前增强  
儿子找对象  
媒婆找对象, 代理方法之后增强  

JDK动态代理实现过程

  1. 拿到代理类的引用,并且获取代理类的所有接口(通过反射)
  2. 使用 JDK 的 Proxy 生成一个新的类,实现了被代理类所有的接口方法
  3. 动态生成 Java 代码,把增强逻辑加入到新生成的代码中
  4. 编译生成新的 Java 代码
  5. 加载并运行新的 Class,得到的类就是生成的新的代理类

CGLib 的动态代理

CGLib 通过继承方式代理类

    <dependency>  
      <groupId>cglib</groupId>  
      <artifactId>cglib</artifactId>  
      <version>3.3.0</version>  
    </dependency>  

实现拦截器

public class CGLibMeipo implements MethodInterceptor {  
    public Object getInstance(Class clazz) {  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(clazz);  
        enhancer.setCallback(this);  
        return enhancer.create();  
    }  
  
    @Override  
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {  
        before();  
        Object invoke = methodProxy.invokeSuper(o, objects);  
        after();  
        return invoke;  
    }  
  
    private void after() {  
        System.out.println("CGLIB媒婆找对象, 代理方法之后增强");  
    }  
    private void before() {  
        System.out.println("CGLIB媒婆找对象, 代理方法之前增强");  
    }  
}  

被代理的对象

public class Customer {  
    public void findLover(){  
        System.out.println("我是普通人找对象");  
    }  
}  

测试类

public class CGLibMeipoTest {  
    public static void main(String[] args) {  
        //利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘  
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/niuyy/Desktop");  
        Customer customer = (Customer) new CGLibMeipo().getInstance(Customer.class);  
        customer.findLover();  
    }  
}CGLIB媒婆找对象, 代理方法之前增强  
我是普通人找对象  
CGLIB媒婆找对象, 代理方法之后增强  

原理:

  1. 生成代理类覆盖所有被代理类的方法,并且每个方法都有一个 MethodProxy 与之对应
  2. 代理对象调用 this.findLover() 方法 -> 调用拦截器 -> methodProxy.invokeSuper -> CGLIB$findLove$0 -> 被代理对象 findLove() 方法

生成的代理类的部分截图

image.png

CGLib 动态代理执行代理方法效率之所以比 JDK 的高,是因为 CGLib 采用了 FastClass 机制

它的原理简单来说就是为代理类和被代理类各生成一个 Class,这个 Class 会为代理类或被代理类的方法分配一个 index(int 类型)。这个 index 当做一个入参,FastClass 就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK 动态代理通过反射调用高。

JDK 和 CGLib 对比

  1. JDK 实现接口,CGLib继承类
  2. JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低
  3. JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法, CGLib 执行效率更高

Spring 和动态代理

当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理 当 Bean 没有实现接口时,Spring 选择 CGLib。

Spring 通过配置全局使用 CGLib 代理

<aop:aspectj-autoproxy proxy-target-class="true"/>  

JDK 动态代理的接口为什么不能超过 65535

背景知识:Class 文件是一组以 8 字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列在 class 文件中,中间没有任何分隔符,这使得 class 文件中存储的内容几乎是全部程序运行的程序

Java 虚拟机规范规定,Class 文件格式采用类似 C 语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表接口索引计数器(interfaces_count),占 2 字节

所以:class 文件是一组 8 字节为基础的二进制流,interface_count 占 2 字节。也就是 16.00000000,00000000,  所以,interface_count 的数量最多是 2^16 次方, 也就是最大值 = 65535

这是在 JVM 的层面上决定了它的数量最多是 65535

且在 Java 源码中也可以看到

if (var2.size() > 65535) {  
throw new IllegalArgumentException("interface limit exceeded: " + var2.size());  

直接做了 65535 的长度的校验,所以,JDK 的动态代理要求目标类实现的接口数量不能超过65535

原型模式

  • 定义:属于创建型模式,实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 
  • 优点:调用者不用关心任何细节,不调用构造函数

适用场景:

  • 类消耗资源多
  • new 对象过程繁琐
  • 构造函数复杂
  • 循环体中产生大量对象
public class SysUser implements Serializable {  
  
    private static final long serialVersionUID = 42L;  
  
    private String username;  
    private String password;  
  
    public SysUser(){  
    }  
  
    public SysUser(SysUserVO vo){  
        this.setPassword(vo.getPassword());  
        this.setUsername(vo.getPassword());  
    }  
    // GETTER / SETTER  
}  
public class SysUserVO {  
    private String username;  
    private String password;  
    // GETTER / SETTER  
}  
public class PrototypeTest {  
    public static void main(String[] args) throws CloneNotSupportedException {  
        SysUserVO vo = new SysUserVO();  
          
        //老方式  
        SysUser user = new SysUser();  
        user.setPassword(vo.getPassword());  
        user.setUsername(vo.getPassword());  
        //原型模式  
        SysUser user1 = new SysUser(vo);  
    }  
}  

深克隆&浅克隆

利用序列化完成深克隆(序列化一定会创建对象)

public class Cat implements Serializable {  
    private static final long serialVersionUID = 42L;  
    private SysUser sysUser;  
  
    @Override  
    protected Object clone() throws CloneNotSupportedException {  
        return this.deepClone();  
    }  
  
    private Object deepClone() {  
        try {  
            ByteArrayOutputStream bos = new ByteArrayOutputStream();  
            ObjectOutputStream oos = new ObjectOutputStream(bos);  
            oos.writeObject(this);  
  
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
            ObjectInputStream ois = new ObjectInputStream(bis);  
            return ois.readObject();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
    // GETTER / SETTER  
//利用序列化进行深克隆  
Cat cat = new Cat();  
cat.setSysUser(new SysUser());  
  
Cat clone = (Cat) cat.clone();  
System.out.println(cat.getSysUser() == clone.getSysUser());//false  

建造者模式

  • 定义:链式调用创建对象。区分于原型模式:原型模式创建对象的步骤是固定的
  • 优点:封装性好,创建和使用分离扩展性好,建造类独立,一定程度上解耦
  • 缺点:产生多余的 Builder 对象;产品内部发生变化,建造者都要修改,成本较大

适用场景

  • 创建对象需要很多步骤,但是步骤的顺序不一定
  • 一个对象的属性很多
  • 把复杂对象的创建和使用分离

这里直接介绍结合 lombok 插件的应用

public class BuilderTest {  
    @Data  
    // Accessors 设置 chain 为 true,会使 set 方法返回自身  
    @Accessors(chain = true)  
    public static class A {  
        private String name;  
    }  
    @Setter  
    @Getter  
    @ToString  
    // 构造者模式,会创建一个当前类的 Builder,但是当前类就没有无参构造函数了  
    @Builder  
    public static class B {  
        private String name;  
    }  
  
    public static void main(String[] args) {  
        A a = new A().setName("张三");  
        System.out.println(a);  
        B b = new B.BBuilder()  
                .name("李四")  
                .build();  
        System.out.println(b);  
    }  
}  

注解分别为类生成的方法如图

image.png

  • A 的 set 方法返回了自身
  • B 中生成了一个内部类 BBuilder,以及一个全参构造器

委派模式

  • 定义:属于行为型模式,负责任务的调度和分配任务,跟代理模式很像,代理模式注重过程,委派模式注重结果

老板

public class Boss {  
    public void work(Manager manager, String command){  
        manager.work(command);  
    }  
}  

员工

public interface Employee {  
    void work();  
}  
public class EmployeeA implements Employee{  
    @Override  
    public void work(){  
        System.out.println("员工A要编程");  
    }  
}  
public class EmployeeB implements Employee {  
    @Override  
    public void work() {  
        System.out.println("员工B要设计");  
    }  
}

管理者

public class Manager {  
  
    private Map<String, Employee> employeeMap = new HashMap<>();  
    public Manager() {  
        employeeMap.put("编程"new EmployeeA());  
        employeeMap.put("设计"new EmployeeB());  
    }  
    public void work(String command) {  
        Employee employee = employeeMap.get(command);  
        if (Objects.nonNull(employee)) {  
            employee.work();  
        } else {  
            System.out.println("没有擅长的员工");  
        }  
    }  
}  

测试

public class SimpleTest {  
    public static void main(String[] args) {  
        new Boss().work(new Manager(), "编程");  
        new Boss().work(new Manager(), "设计");  
        new Boss().work(new Manager(), "吃饭");  
    }  
}
员工A要编程  
员工B要设计  
没有擅长的员工  

Spring 中的委派

Spring 中带有 delegate 的基本都是委派,BeanDefinitionParserDelegate

image.png

模拟 SpringMVC,新建 Controller

public class SysCompanyController {  
    public void getCompById(Long id) {  
        System.out.println("getCompById");  
    }  
}  
public class SysMenuController {  
    public void getMenuById(Long id) {  
        System.out.println("getMenuById");  
    }  
}  
public class SysUserController {  
    public void getUserById(Long id) {  
        System.out.println("getUserById");  
    }  
}  

请求分发

public class DispatcherServlet extends HttpServlet {  
    private void dispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {  
        String uri = req.getRequestURI();  
        Long id = Long.parseLong(req.getParameter("id"));  
        if ("getCompById".equals(uri)) {  
            new SysCompanyController().getCompById(id);  
        } else if ("getUserById".equals(uri)) {  
            new SysUserController().getUserById(id);  
        } else if ("getMenuById".equals(uri)) {  
            new SysMenuController().getMenuById(id);  
        } else {  
            resp.getWriter().write("404 NOT FOUND!");  
        }  
    }  
}  

策略模式

  • 定义:定义一个算法分组,分别封装起来,让他们之间可以相互替换
  • 优点:此模式让算法的变换不会影响使用算法的用户,符合开闭原则;可以避免多分支的 if else 语句;提高算法的安全性和保密性
  • 缺点:客户必须知道所有策略,才能知道使用哪个策略;代码中很多策略类,难维护

使用场景:

  • 系统中有很多类,区别在于他们的行为不同
  • 一个系统需要在几种算法中动态的选择一种

优惠策略

public interface PromotionStrategy {  
    void promotion();  
}

没有优惠 / 返现优惠 / 组团优惠

public class EmptyPromotion implements PromotionStrategy {  
    @Override  
    public void promotion() {  
        System.out.println("没有优惠");  
    }  
}  
public class CashBackPromotion implements PromotionStrategy {  
    @Override  
    public void promotion() {  
        System.out.println("返现优惠");  
    }  
}  
public class GroupByPromotion implements PromotionStrategy {  
    @Override  
    public void promotion() {  
        System.out.println("组团优惠");  
    }  
}

使用工厂模式创建优惠策略:

public class PromotionStrategyFactory {  
    private final static Map<String, PromotionStrategy> MAP = new HashMap<>();  
    private static final PromotionStrategy EMPTY = new EmptyPromotion();  
  
    static {  
        MAP.put(PromotionKey.CASH, new CashBackPromotion());  
        MAP.put(PromotionKey.GROUPBY, new GroupByPromotion());  
    }  
  
    private PromotionStrategyFactory(){}  
  
    public static PromotionStrategy getPromotionStrategy(String promotionKey){  
        PromotionStrategy promotionStrategy = MAP.get(promotionKey);  
        return promotionStrategy == null ? EMPTY : promotionStrategy;  
    }  
}  
public interface PromotionKey {  
    String CASH = "CASH";  
    String GROUPBY = "GROUPBY";  
}  

测试

public class PromotionTest {  
    public static void main(String[] args) {  
        PromotionStrategy promotionStrategy = PromotionStrategyFactory.getPromotionStrategy(PromotionKey.CASH);  
        promotionStrategy.promotion();  
    }  
}
返现优惠

之后如果需要添加优惠政策,只需要新建类,符合开闭原则

改造 DispatcherServlet

DispatcherServlet 来自上文委派模式中的示例

public class DispatcherServlet extends HttpServlet {  
    private List<Handler> handlerList = new ArrayList<>();  
  
    @Override  
    public void init() throws ServletException {  
        Class<SysUserController> controllerClass = SysUserController.class;  
        try {  
            handlerList.add(new Handler("getUserById"new SysUserController(),  
                    controllerClass.getMethod("getUserById", Long.class)));  
        } catch (NoSuchMethodException e) {  
            e.printStackTrace();  
        }  
        super.init();  
    }  
      
    @Override  
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
        try {  
            dispatch(req, resp);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
      
    private void dispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {  
        String uri = req.getRequestURI();  
        Long id = Long.parseLong(req.getParameter("id"));  
        for (Handler handler : handlerList) {  
            if (Objects.equals(handler.getUri(), uri)) {  
                Object invoke = handler.getMethod().invoke(handler.getController(), id);  
                resp.getWriter().write(invoke.toString());  
                break;  
            }  
        }  
    }  
      
  
    class Handler {  
        private String uri;  
        private Object controller;  
        private Method method;  
  
        public Handler(String uri, Object controller, Method method) {  
            this.uri = uri;  
            this.controller = controller;  
            this.method = method;  
        }  
  
        // SETTER/GETTER  
    }  
}

其实说简单点就是把所有的策略放在一个地方,然后根据指定的 key 取出调用

Spring 中的策略模式

image.png

门面模式

  • 定义:(Facade Pattern)结构性模式,也叫外观模式,提供了一个接口,用来访问子系统的一群接口,器主要特征是定义了一个接口,让子系统更容易使用
  • 优点:简化调用过程,无需深入了解子系统;减少系统依赖,低耦合;划分访问层次,提高安全性;遵循迪米特法则,也就是最少知道原则
  • 缺点:增加和扩展子系统行为时,可能容易带来未知风险;不符合开闭原则

使用场景:

  • 子系统越来越复杂,增加门面模式提供简单的接口
  • 构建多层系统结构,利用门面对象作为每层的入口,简化层间调用

门面模式 UML 图

image.png

如图有三个子系统

public class SubSystemA {
    public void doA(){}
}
public class SubSystemB {
    public void doB(){}
}
public class SubSystemC {
    public void doC(){}
}

使用门店封装子系统接口

public class Facade {
    private SubSystemA a = new SubSystemA();
    private SubSystemB b = new SubSystemB();
    private SubSystemC c = new SubSystemC();
    public void doA(){
        this.a.doA();
    }
    public void doB(){
        this.b.doB();
    }
    public void doC(){
        this.c.doC();
    }
}

然后调用方式如下

public class FacadeTest {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.doA();
        facade.doB();
        facade.doC();
    }
}

Spring 中的门面模式

JdbcUtils 就是封装了对 Connection / Statement / ResultSet 的一系列操作

image.png

装饰器模式

  • 定义:(Decorator Pattern)结构性模式,又叫包装模式。在不改变原对象的基础之上,将功能附加到对象上
  • 优点:是继承的有力补充,比继承灵活;通过不同装饰类以及这些装饰类的排列组合,可以实现不同效果,遵循开闭原则
  • 缺点:类会变多,增加程序复杂性

使用场景:

  • 扩展一个类的功能或者给一个类添加附加职责
  • 动态给一个对象添加功能,这些功能可以再动态的撤销

装饰器模式 UML 图

企业微信截图_16687567407219.png

煎饼类及其包装类

public abstract class BaseCake {
    public abstract void content();
    public abstract void price();
    public void print() {
        this.content();
        this.price();
    }
}

public class DefaultCake extends BaseCake {
    @Override
    public void content() {
        System.out.println("我是默认方案, 一个蛋");
    }

    @Override
    public void price() {
        System.out.println("我的价格是 5 元");
    }
}

public class DefaultCakeDecorator extends BaseCake {
    private BaseCake baseCake;

    public DefaultCakeDecorator(BaseCake baseCake){
        this.baseCake = baseCake;
    }

    @Override
    public void content() {
        baseCake.content();
    }
    @Override
    public void price() {
        baseCake.price();
    }
}

public class EggCakeDecorator extends BaseCake {
    private BaseCake baseCake;

    public EggCakeDecorator(BaseCake baseCake){
        this.baseCake = baseCake;
    }
    @Override
    public void content() {
        baseCake.content();
        System.out.println("加了一个蛋");
    }
    @Override
    public void price() {
        baseCake.price();
        System.out.println("加了 1 元");
    }
}

public class SausageCakeDecorator extends BaseCake {
    private BaseCake baseCake;

    public SausageCakeDecorator(BaseCake baseCake){
        this.baseCake = baseCake;
    }
    @Override
    public void content() {
        baseCake.content();
        System.out.println("加了一个香肠");
    }
    @Override
    public void price() {
        baseCake.price();
        System.out.println("加了 2 元");
    }
}

public class DecoratorTest {
    public static void main(String[] args) {
        BaseCake baseCake = new DefaultCake();
        baseCake = new DefaultCakeDecorator(baseCake);
        baseCake.print();
        System.out.println("-----------");
        baseCake = new EggCakeDecorator(baseCake);
        baseCake.print();
        System.out.println("-----------");
        baseCake = new SausageCakeDecorator(baseCake);
        baseCake.print();
    }
}

JDK 中的装饰器模式

image.png

享元模式

  • 定义:(Flyweight Pattern)结构性模式,又称轻量级模式。是对象池的一种实现,类似于线程池,减少对象数量从而改善应用所需的对象结构
  • 优点:减少对象创建,降低内存中对象的数量;减少内存之外的其他资源占用
  • 缺点:需要关注资源内外部状态,线程安全问题;系统逻辑复杂化

使用场景:

  • 系统中多处需要同一组信息,可以把这些信息封装到一个对象中,
  • 系统有大量相似对象,需要缓冲池
  • 系统底层开发,以便解决系统性能问题

享元模式 UML 图

image.png

享元角色 TrainTicket 和享元工厂 TicketFactory

public interface ITicket {
    void showInfo(String bunk);
}
public class TrainTicket implements ITicket {

    private String from;
    private String to;
    private int price;

    public TrainTicket(String from, String to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void showInfo(String bunk) {
        this.price = new Random().nextInt(500);
        System.out.printf("%s->%s:%s, 价格:%s 元\n", from, to, bunk, price);
    }
}

public class TicketFactory {
    public static ITicket queryTicket(String from, String to) {
        return new TrainTicket(from, to);
    }

    private static Map<String, ITicket> pool = new ConcurrentHashMap<>();
    public static ITicket flyweightQueryTicket(String from, String to) {
        String key = "from" + "->" + to;
        if (pool.containsKey(key)) {
            System.out.println("使用缓存:" + key);
            return pool.get(key);
        }

        System.out.println("首次查询,创建对象:" + key);
        TrainTicket ticket = new TrainTicket(from, to);
        pool.put(key,ticket);
        return ticket;
    }
}
public class FlyWeightTest {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
//            ITicket ticket = TicketFactory.queryTicket("北京", "上海");
            ITicket ticket = TicketFactory.flyweightQueryTicket("北京", "上海");
            ticket.showInfo("硬座");
        }
    }
}

Java 中的享元模式

String 使用双引号声明时会被保存在字符串常量池中,Integer 在 -128 至 127 之前也是缓存,Long 也有缓存

public class StringTest {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = "he" + "llo";
        String s4 = "he" + new String("llo");
        String s5 = new String("hello");
        String s6 = s5.intern();
        String s7 = "h";
        String s8 = "ello";
        String s9 = s7 + s8;
        System.out.println(s1 == s2);// true,相同字符串存在常量池中有且只有一个缓存
        System.out.println(s1 == s3);// true,JVM 在编译期间已经优化将结果 hello 缓存
        System.out.println(s1 == s4);// false,new String("llo") 是两个对象,new String("llo") 是在堆中的,"llo" 在常量池中,JVM 不优化
        System.out.println(s1 == s9);// false,是两个对象的相加,JVM 不会优化
        System.out.println(s4 == s5);// false,相当于堆中的两个对象,不相等
        System.out.println(s1 == s6);// true,intern 方法是将一个位于堆中的字符串在运行期间动态的加入到字符串常量池(字符串常量池的内
        // 容是程序启动的时候就已经加载好了),如果字符串常量池中有该对象的字面量,则返回其引用,否则,创建
    }
}

XX模式

  • 定义:xxx
  • 优点:xxx
  • 缺点:xxx

使用场景:

  • xxx

XX模式 UML 图