Spring中常用的设计模式有哪些?

x33g5p2x  于2021-08-07 转载在 Spring  
字(31.5k)|赞(0)|评价(0)|浏览(323)

简介

此文基于之前看【Spring核心原理】整理的学习笔记,建议手撸一遍代码加深印象。

一. Spring用到的设计模式类别

1. 创建型模式

  • 工厂模式(Factory Pattern)
  • 单例模式 (Singleton Pattern)
  • 原型模式 (Prototype Pattern)

2. 结构性模式

  • 适配器模式 (Adapter Pattern)
  • 装饰者模式 (Decorator Pattern)
  • 代理模式 (Proxy Pattern)

3. 行为型模式

  • 策略模式 (Strategy Pattern)
  • 模板模式 (Template Pattern)
  • 委托模式 (Delegate Pattern)
  • 观察者模式 (Observer Pattern)

二. 设计模式详解

1. 工厂模式

1.1 简单工厂模式

简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建哪一种产品类的实例,但它不属于GoF的23种设计模式.
通过反射的方式生成对象
缺点: 工厂类的职责相对过重,不易于扩展过于复杂的逻辑代码

public interface Course { 
    void record();
}
public class JavaCourse implements Course { 
    @Override
    public void record() { 
        System.out.println("java课程");
    }
}
public class CourseFactory { 
    public static Course getCourse(Class<? extends Course> clas) { 
        try { 
            if (null != clas) { 
                return clas.newInstance();
            }
        } catch (Exception e) { 
            e.printStackTrace();
        }
        return null;
    }
    public static void main(String[] args) { 
        Course course=CourseFactory.getCourse(JavaCourse.class);
        course.record();
    }
}

1.2 工厂方法模式

工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法模式让类的实例化推迟到子类进行,在工厂方法模式中用户只需要关心所需产品对应的工厂,无需关心创建细节,而加入新的产品也符合开闭原则

public interface Course { 
    void record();
}
public class JavaCourse implements Course { 
    @Override
    public void record() { 
        System.out.println("java课程");
    }
}
public interface CourseFactory { 
    Course getCourse();
}
public class JavaCourseFacotry implements CourseFactory { 
    @Override
    public Course getCourse() { 
        return new JavaCourse();
    }
    public static void main(String[] args) { 
        Course course = new JavaCourseFacotry().getCourse();
        course.record();
    }
}

1.3 抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是指提供一个创建一系列相关或者相关依赖对象的接口,无需指定他们的具体实现类.
spring中应用得最广泛的一种设计模式

public interface Note { 
    void edit();
}
public interface Video { 
    void record();
}
public class JavaNote implements Note { 
    @Override
    public void edit() { 
        System.out.println("编写Java笔记");
    }
}
public class JavaVideo implements Video { 
    @Override
    public void record() { 
        System.out.println("录制Java视频");
    }
}
public interface CourseFactory { 
    Note getNote();
    Video getVideo();
}
public class JavaCourseFactory implements CourseFactory { 
    @Override
    public Note getNote() { 
        return new JavaNote();
    }
    @Override
    public Video getVideo() { 
        return new JavaVideo();
    }
    public static void main(String[] args) { 
        CourseFactory courseFactory = new JavaCourseFactory();
        courseFactory.getNote().edit();
        courseFactory.getVideo().record();
    }
}

2. 单例模式

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,单例模式是创建型模式,Spring框架中ApplicationContext,数据库的连接池等也是单例形式

2.1 饿汉模式

饿汉单例模式在类加载的时候就立即初始化,并且创建对象了,它绝对线程安全,在线程还没出现之前就实例化了,不存在访问安全问题
缺点: 类加载时候就初始化,不管用与不用都占用空间,浪费内存

public class HungrySingleton { 
    private static final HungrySingleton INSTANCE = new HungrySingleton();
    private HungrySingleton() {  }
    public static HungrySingleton getInstance() { 
        return INSTANCE;
    }
}

2.2 懒汉模式

懒汉模式的特点是: 被外部类调用的时候内部类才会初始化
下面采用内部类方式实现

public class LazySingleton { 
    private LazySingleton() {  }
    public static LazySingleton getInstance() { 
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder { 
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}

2.3 破坏单例的方式

2.3.1反射破坏

public class Main { 
    public static void main(String[] args) throws Exception { 
        LazySingleton s1 = LazySingleton.getInstance();
        //无聊的情况下,进行破坏
        Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();
        //强制访问
        constructor.setAccessible(true);
        //暴力初始化
        Object s2 = constructor.newInstance();
        System.out.println(s1 == s2);
    }
}

改善LazySingleton防止反射破坏

public class LazySingleton { 
    private LazySingleton() { 
        if(LazyHolder.INSTANCE!=null){ 
            throw  new RuntimeException("不允许创建多个实例");
        }
    }
    public static LazySingleton getInstance() { 
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder { 
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}

2.3.2序列化破坏单例

public class SeriableSingleton  implements Serializable { 
    private static final SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton() {  }
    public static SeriableSingleton getInstance() { 
        return INSTANCE;
    }
}
    public static void main(String[] args) { 
        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();
        FileOutputStream fos = null;
        try { 
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();
            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SeriableSingleton) ois.readObject();
            ois.close();
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1==s2);
        } catch (Exception e) { 
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

添加readResolve()方法即修复此问题

public class SeriableSingleton  implements Serializable { 
    private static final SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton() {  }
    public static SeriableSingleton getInstance() { 
        return INSTANCE;
    }
    private Object readResolve() { 
        return INSTANCE;
    }
}

在这里插入图片描述

ObjectInputStream
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,分析一下ObjectInputputStream 的readObject 方法执行情况到底是怎样的。

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    { 
        //此处省略部分代码
        Object obj;
        try { 
        //这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject返回的对象。
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) { 
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        //此处省略部分代码
        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) { 
                handles.setObject(passHandle, obj = rep);
            }
        }
        return obj;
    }

isInstantiable:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。
*
desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。
*
hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true
*
invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。

所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

为什么序列化可以破坏单例了?
答:序列化会通过反射调用无参数的构造方法创建一个新的对象。

2.4 注册式单例模式

注册式单例模式又称登记式单例模式,都是将每一个实例登记到某个地方,使用唯一的标识来获取实例.注册式单例模式有两种:一种为枚举式单例模式,另一种为容器式单例模式

2.4.1 枚举单例模式

public enum EnumSingleton { 
    INSTANCE;
    private User user;
    public User getUser() { 
        return user;
    }
    public void setUser(User user) { 
        this.user = user;
    }
    public static EnumSingleton getInstance() { 
        return INSTANCE;
    }
    public static class User { 
    }
    public static void main(String[] args) { 
        EnumSingleton s1 = EnumSingleton.getInstance();
        EnumSingleton s2 = EnumSingleton.getInstance();
        //true
        System.out.println(s1 == s2);
    }
}

测试反射破坏枚举单例模式

EnumSingleton s1 = EnumSingleton.getInstance();
        //无聊的情况下,进行破坏
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor();
        //强制访问
        constructor.setAccessible(true);
        //暴力初始化
        Object s2 = constructor.newInstance();
        System.out.println(s1 == s2);

下面异常显示没有找到构造方法,枚举类只有protected类型的构造方法
在这里插入图片描述

测试序列化破坏枚举单例模式

EnumSingleton s1 = null;
        EnumSingleton s2 = EnumSingleton.getInstance();
        FileOutputStream fos = null;
        try { 
            fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();
            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (EnumSingleton) ois.readObject();
            ois.close();
            //输出true
            System.out.println(s1==s2);
        } catch (Exception e) { 
            e.printStackTrace();
        }

查看ObjectInputStream的readObject0方法
在这里插入图片描述

readEnum方法实现如下

private Enum<?> readEnum(boolean unshared) throws IOException { 
        if (bin.readByte() != TC_ENUM) { 
            throw new InternalError();
        }
        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) { 
            throw new InvalidClassException("non-enum class: " + desc);
        }
        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) { 
            handles.markException(enumHandle, resolveEx);
        }
        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) { 
            try { 
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) { 
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) { 
                handles.setObject(enumHandle, result);
            }
        }
        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

枚举类型其实通过类名和类对象类找到一个唯一的枚举对象,因此,枚举对象不可能被加载器加载多次

2.4.2 容器式单例模式

spring中的AbstractAutowireCapableBeanFactory

public class ContainerSingleton { 
    private ContainerSingleton() { 
    }
    private static Map<String, Object> ioc = new ConcurrentHashMap<>();
    public static Object getBean(String classsName) { 
        synchronized (ioc) { 
            if (!ioc.containsKey(classsName)) { 
                Object obj = null;
                try { 
                    obj = Class.forName(classsName).newInstance();
                    ioc.put(classsName, obj);
                } catch (Exception e) { 
                    e.printStackTrace();
                }
                return obj;
            } else { 
                return ioc.get(classsName);
            }
        }
    }
    public static void main(String[] args) { 
        for(int i=0;i<10;i++){ 
           new Thread(()->{ 
               System.out.println(getBean(User.class.getName()));
           }).start();
        }
    }
}

3. 原型模式详解

原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过复制这些原始创建新的对象.
在Spring中,原型模式应用得非常广泛,例如scope=“prototype”.

3.1 浅克隆

浅克隆只复制了属性的值,但是没有赋值引用对象

public interface Prototype { 
    Prototype clone();
}
public class BeanUtil { 
    private BeanUtil(){ }
    public static Prototype clone(Prototype prototype) { 
        return prototype.clone();
    }
}
public class User implements Prototype { 
    private String username;
    private List<String> hobbyList;
    public String getUsername() { 
        return username;
    }
    public void setUsername(String username) { 
        this.username = username;
    }
    public List<String> getHobbyList() { 
        return hobbyList;
    }
    public void setHobbyList(List<String> hobbyList) { 
        this.hobbyList = hobbyList;
    }
    @Override
    public Prototype clone() { 
        User user = new User();
        user.username = this.username;
        user.hobbyList = this.hobbyList;
        return user;
    }
}
    public static void main(String[] args) { 
        User user = new User();
        user.setUsername("test");
        user.setHobbyList(Arrays.asList("撸铁"));
        User user1 = (User) BeanUtil.clone(user);
        user1.getHobbyList().set(0,"写博客");
        //输出 写博客,true
        System.out.println(user.getHobbyList().get(0));
    }

由于List是引用类型,所以user和user1的hobbyList是一个对象
在这里插入图片描述

3.2 深克隆

深克隆在克隆属性的同时,通过序列化反射生成了一个新的引用地址

public class User implements Cloneable, Serializable { 
    private String username;
    private List<String> hobbyList;
    public String getUsername() { 
        return username;
    }
    public void setUsername(String username) { 
        this.username = username;
    }
    public List<String> getHobbyList() { 
        return hobbyList;
    }
    public void setHobbyList(List<String> hobbyList) { 
        this.hobbyList = hobbyList;
    }
    @Override
    public Object clone() throws CloneNotSupportedException{ 
         //Cloneable.clone接口默认是浅克隆
        //return super.clone();
        return this.deepClone();
    }
    public 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);
            User copy = (User) ois.readObject();
            return copy;
        } catch (Exception e) { 
            e.printStackTrace();
            return null;
        }
    }
}

public static void main(String[] args) { 
        User user = new User();
        user.setUsername("test");
        user.setHobbyList(Arrays.asList("撸铁"));
        User user1 =(User) user.clone();
        user1.getHobbyList().set(0, "写博客");
        //输出 撸铁 false
        System.out.println(user.getHobbyList().get(0));
        System.out.println(user.getHobbyList() == user1.getHobbyList());
}

4. 代理模式

代理模式(Proxy Pattern)给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

4.1 为什么要用代理模式?

  • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

4.2 静态代理

  • 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
  • 缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
public interface BuyHouse { 
    void buyHosue();
}
public class BuyHouseImpl  implements BuyHouse{ 
    @Override
    public void buyHosue() { 
        System.out.println("我要买房");
    }
}
public class BuyHouseProxy implements BuyHouse { 
    private BuyHouse buyHouse;
    public BuyHouseProxy(final BuyHouse buyHouse) { 
        this.buyHouse = buyHouse;
    }
    @Override
    public void buyHosue() { 
        System.out.println("买房前准备钱");
        buyHouse.buyHosue();
        System.out.println("和中介签订合同");
    }
}
public class Main { 
    public static void main(String[] args) { 
        BuyHouse buyHouse = new BuyHouseImpl();
        buyHouse.buyHosue();
        BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
        buyHouseProxy.buyHosue();
    }
}

4.3 动态代理

public class DynamicProxyHandler implements InvocationHandler { 
    private Object object;
    public DynamicProxyHandler(final Object object) { 
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        System.out.println("买房前准备");
        Object result = method.invoke(object, args);
        System.out.println("和中介签订合同");
        return result;
    }
}
public class Main { 
    public static void main(String[] args) { 
        BuyHouse buyHouse = new BuyHouseImpl();
        BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
                Class[]{ BuyHouse.class}, new DynamicProxyHandler(buyHouse));
        proxyBuyHouse.buyHosue();
    }
}

注意Proxy.newProxyInstance()方法接受三个参数:

  • ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
  • Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
    动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质。

4.4 CGLIB代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

public class BuyHouseTwo { 
    public void buyHosue() { 
        System.out.println("我要买房");
    }
}
public class CglibProxy implements MethodInterceptor { 
    public Object getProxy(Class clas) { 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clas);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /** * @param obj 表示要进行增强的对象 * @param method 表示拦截的方法 * @param args 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用 * @return 执行结果 */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 
        System.out.println("买房前准备");
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("买房后装修");
        return result;
    }
}
public class Main { 
    public static void main(String[] args) { 
        CglibProxy cglibProxy = new CglibProxy();
        BuyHouseTwo proxy  = (BuyHouseTwo) cglibProxy.getProxy(BuyHouseTwo.class);
        proxy .buyHosue();
    }
}

在这里插入图片描述

CGLIB代理总结: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

4.5 JDK动态代理与CGLIB对比

  • JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象。
  • CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy的优势:
最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。

CGLIB的优势:
无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。

4.6 代理模式在Spring中的应用

ProxyFactoryBean核心方法getObject()

public Object getObject() throws BeansException { 
        this.initializeAdvisorChain();
        if (this.isSingleton()) { 
            return this.getSingletonInstance();
        } else { 
            if (this.targetName == null) { 
                this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
            }
            return this.newPrototypeInstance();
        }
    }

在spring的配置中如果不做任何处理设置,那么spring代理的类都是单例对象,如果修改scope为prototype,则每次创建一个新的对象

spring bean的scope属性

  • singleton 表示在spring容器中的单例,通过spring容器获得该bean时总是返回唯一的实例(默认)
  • prototype表示每次获得bean都会生成一个新的对象

Spring利用动态代理实现AOP时有两个非常重要的类,JdkDynamicAopProxy类和CglibAopProxy类
在这里插入图片描述

Spring中的代理选择原则

  • 当Bean有实现接口时,Spring就会用JDK动态代理
  • 当Bean没有实现接口时,Spring会选择CGLib代理
  • spring可以通过配置强制使用CGLib代理

4.7 代理模式的优缺点

优点

  • 1.代理模式能将代理对象与真实被调用目标对象分离
  • 2.在一定程序上降低了系统的耦合性,扩展性好
  • 3.可以起到保护目标对象的功能
  • 4.可以增强目录对象的功能

缺点

  • 1.代理模式会造成系统设计中类的数量增加
  • 2.在客户端和目标对象中增加一个代理对象,会导致请求速度会变慢
  • 3.增加了系统的复杂度

5. 委派模式

委派模式(Delegate Pattern)不属于GoF 23种设计模式.在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟mixin。

委派模式和代理模式很像,可以看作是一种特殊情况下的静态全局代理,但是代理模式注重过程,委托模式注重结果, web开发中常用的DispatcherServlet就用到了委托模式.在Spring源码中,以Delegate结尾的地方都实现了委派模式

本文以找黄牛买票为例

/** * @description 票类接口 **/
public interface Ticket { 
    void doing();
}

public class TrainTicket implements Ticket { 
    @Override
    public void doing() { 
        System.out.println("买到南昌的飞机票");
    }
}
public class ConcertTicket implements Ticket { 
    @Override
    public void doing() { 
        System.out.println("买许嵩的演唱会票"); //黄牛
    }
}
public class Cattle { 
    public void doing(Ticket ticket) { 
        System.out.println("我是黄牛");
        ticket.doing();
        System.out.println("买票结束");
    }
}
public class Main { 
    public static void main(String[] args) { 
        Cattle cattle=new Cattle();
        cattle.doing(new ConcertTicket());
        cattle.doing(new TrainTicket());
    }
}

6. 策略模式

策略模式(Strategy Pattern)是指定义了算法家族并分别封装起来,让它们可以相互替换,此模式使算法的变化不会影响使用算法的用户.

下面以网购买完东西,结账时候选择支付方式为例

public abstract class Payment { 
    //支付类型
    public abstract String getName();
    //查询余额
    protected abstract double queryBalance(String uid);
    //扣款支付
    public PayState pay(String uid,double amount){ 
        if(queryBalance(uid)<amount){ 
            return new  PayState(500,"支付失败","余额不足");
        }
        return new PayState(200,"支付成功","交易金额:"+amount);
    }
}
public class PayState { 
    private int code;
    private Object data;
    private String msg;
    public PayState(int code,String msg,Object data){ 
        this.code=code;
        this.msg=msg;
        this.data=data;
    }
    @Override
    public String toString(){ 
        return "支付状态:["+code+"],"+msg+",交易详情:"+data;
    }
}
public class AliPay extends Payment { 
    @Override
    public String getName() { 
        return "支付宝";
    }

    @Override
    protected double queryBalance(String uid) { 
        return 900;
    }
    @Override
    public PayState pay(String uid, double amount) { 
        return super.pay(uid, amount);
    }
}
public class WechatPay extends Payment { 
    @Override
    public String getName() { 
        return "微信支付";
    }
    @Override
    protected double queryBalance(String uid) { 
        return 257;
    }
    @Override
    public PayState pay(String uid, double amount) { 
        return super.pay(uid, amount);
    }
}
public class PayStrategy { 
    public static final String ALI_PAY = "AliPay";
    public static final String WECHAT_PAY = "WechatPay";
    public static final String DEFAULT_PAY = ALI_PAY;
    private static Map<String, Payment> payStrategy = new HashMap<String, Payment>();
    static { 
        payStrategy.put(ALI_PAY, new AliPay());
        payStrategy.put(WECHAT_PAY, new WechatPay());
    }
    /** * 根据key选择对应的策略 */
    public static Payment get(String payKey) { 
        if (!payStrategy.containsKey(payKey)) { 
            return payStrategy.get(DEFAULT_PAY);
        }
        return payStrategy.get(payKey);
    }
}
public class Order { 
    private String uid;
    private String orderId;
    private double amount;

    public Order(String uid, String orderId, double amount) { 
        this.uid = uid;
        this.orderId = orderId;
        this.amount = amount;
    }
    public PayState pay(String payKey) { 
        Payment payment = PayStrategy.get(payKey);
        System.out.println("欢迎使用" + payment.getName());
        System.out.println("本次校次金额为:" + amount + ",开始扣款.....");
        return payment.pay(uid, amount);
    }
}

public class Main { 
    public static void main(String[] args) { 
        Order order=new Order("1","20210118",389);
        //选择策略支付
        System.out.println(order.pay(PayStrategy.ALI_PAY));
    }
}

策略模式源码中应用场景
1.策略模式在JDK中源码的体验
比较容器-Comparator接口,大家常用的compare()方法就是一个策略模式的抽象实现

@FunctionalInterface
public interface Comparator<T> { 
     int compare(T o1, T o2);
}

2.在Spring中的应用
Spring在初始化也采用了策略模式,即不同类型的类采用不同的初始化策略,
InstantiationStrategy接口

public interface InstantiationStrategy { 
    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3) throws BeansException;

    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, Constructor<?> var4, Object... var5) throws BeansException;

    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, @Nullable Object var4, Method var5, Object... var6) throws BeansException;
}

顶层的策略抽象非常简单,它下面有两种策略: SimpleInstantiationStrategy
CglibSubclassingInstantiationStrategy,

public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy { 
}

CglibSubclassingInstantiationStrategy继承自SimpleInstantiationStrategy类,说明实际应用中多种策略可以继承使用.

策略模式的优缺点:
优点:策略符合开闭原则,策略模式可避免使用多重条件语句,如if…else语句,switch语句,使用策略模式可以提供算法的保密性和安全性
缺点:客户端必须知道所有策略,并且自行决定使用哪个策略

7. 模板模式

模板模式又叫模板方法模式(Template Method Pattern),是指定义一个算法的骨架,并允许子类为一个或者多个步骤提供实现.模板模式使得子类可以在不变算法的前提下,重新定义算法的某些步骤,属于行为型设计模式,模板模式使用与以下场景

  • 1.一次性实现一个算法的不变部分,并将可变的行为交给子类实现
  • 2.各子类中公共行为被提取出来并集中到一个公共的父类中,从而避免代码重复

我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。

TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。
在这里插入图片描述

public abstract class Game { 
    abstract void initialize();
    abstract void startPlay();
    abstract void endPlay();
    //模板
    public final void play(){ 
        //初始化游戏
        initialize();
        //开始游戏
        startPlay();
        //结束游戏
        endPlay();
    }
}
public class Cricket extends Game { 
    @Override
    void endPlay() { 
        System.out.println("Cricket Game Finished!");
    }
    @Override
    void initialize() { 
        System.out.println("Cricket Game Initialized! Start playing.");
    }
    @Override
    void startPlay() { 
        System.out.println("Cricket Game Started. Enjoy the game!");
    }
}
public class Football extends Game { 
    @Override
    void endPlay() { 
        System.out.println("Football Game Finished!");
    }
    @Override
    void initialize() { 
        System.out.println("Football Game Initialized! Start playing.");
    }
    @Override
    void startPlay() { 
        System.out.println("Football Game Started. Enjoy the game!");
    }
}
public class Main { 
    public static void main(String[] args) { 
        Game game = new Cricket();
        game.play();
        System.out.println();
        game = new Football();
        game.play();
    }
}

模式模式的优点

  • 1.利用模板模式将相同处理逻辑的代码放到抽象父类中,可以提供代码的可读性
  • 2.将不同的代码放到不同的子类中,通过对子类的扩展增加新的行为,可以提高的扩展性
  • 3.把不变的行为写在父类,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则.

8. 适配器模式详解

适配器模式(Adapter Pattern)是指将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
使用场景如下

  • 1.已经存在的类和方法和需求不匹配(方法结果相同或相似)的情况
  • 2.适配器模式不是软件初始阶段考虑的设计模式,是随着软件的发展,不同厂家造成的功能类似而接口不同的问题的解决方案。

此处以重构第三方登录适配为例
老系统一般刚开始只有密码登录的系统,随着社会的进步,单纯的密码登录已经满足不了广大网友了,大部分系统已经支持多种登录方式,如QQ登录,微信登录,手机登录,微博登录等.

//登录接口
public class SiginService { 
    /** * 注册 */
    public ResultMsg regist(String username,String password){ 
        return new ResultMsg(200,"注册成功",new User());
    }
    /** * 登录 */
    public ResultMsg login(String username,String password){ 
        return null;
    }
}
//返回结果封装
public class ResultMsg { 
    private int code;
    private String msg;
    private Object data;
    public ResultMsg(int code, String msg, Object data) { 
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public int getCode() { 
        return code;
    }
    public void setCode(int code) { 
        this.code = code;
    }
    public String getMsg() { 
        return msg;
    }
    public void setMsg(String msg) { 
        this.msg = msg;
    }
    public Object getData() { 
        return data;
    }
    public void setData(Object data) { 
        this.data = data;
    }
}
/** * @author Dominick Li * @CreateTime 2021/1/18 22:29 * @description 定义登录适配器规范 **/
public interface LoginAdapter { 
    boolean support(Object adapter);
    ResultMsg login(Object [] param, Object adapter);
}
//QQ登录
public class LoginForQQAdapter implements LoginAdapter { 
    @Override
    public boolean support(Object adapter) { 
        return adapter instanceof LoginForQQAdapter;
    }
    @Override
    public ResultMsg login(Object[] param, Object adapter) { 
        System.out.println("QQ登录");
        return null;
    }
}
//手机号登录
public class LoginForTelAdapter implements LoginAdapter { 
    @Override
    public boolean support(Object adapter) { 
        return adapter instanceof LoginForTelAdapter;
    }
    @Override
    public ResultMsg login(Object[] param, Object adapter) { 
        System.out.println("手机号登录");
        return null;
    }
}
/** * @author Dominick Li * @CreateTime 2021/1/18 22:25 * @description 第三方登录兼容接口 **/
public interface PassportForThird { 
    /** * qq登录 */
    ResultMsg loginForQQ(String id);
    /** * 手机号登录 */
    ResultMsg loginForTelphone(String telphone,String code);
    /** * 注册后自动登录 */
    ResultMsg loginForRegist(String username,String password);
}

/** * @description 第三方登录兼容接口 **/
public interface PassportForThird { 
    /** * qq登录 */
    ResultMsg loginForQQ(String id);
    /** * 手机号登录 */
    ResultMsg loginForTelphone(String telphone,String code);
    /** * 注册后自动登录 */
    ResultMsg loginForRegist(String username,String password);
}
public class PassportForThirdAdapter extends SiginService implements PassportForThird { 
    @Override
    public ResultMsg loginForQQ(String id) { 
        return processLogin(new Object[]{ id}, LoginForQQAdapter.class);
    }
    @Override
    public ResultMsg loginForTelphone(String telphone, String code) { 
        return processLogin(new Object[]{ telphone,code}, LoginForTelAdapter.class);
    }
    @Override
    public ResultMsg loginForRegist(String username, String password) { 
        super.regist(username,password);
        return super.login(username,password);
    }
    /** * 简单工厂模式 */
    private ResultMsg processLogin(Object [] param, Class<? extends LoginAdapter> clas) { 
        try { 
            LoginAdapter adapter = clas.newInstance();
            if (adapter.support(adapter)) { 
                return adapter.login(param, adapter);
            }
            return null;
        } catch (Exception e) { 
            e.printStackTrace();
        }
        return null;
    }
}

public class Main { 
    public static void main(String[] args) { 
        PassportForThird passportForThird=new PassportForThirdAdapter();
        passportForThird.loginForTelphone("12345","123");
    }
}

适配器模式主要解决的是功能兼容问题

适配器模式在Spring源码中的使用

  • 1.如Spring AOP中的AdvisorAdapter类,它有三个实现类: MethodBeforeAdviceAdapter,AfterReturningAdviceAdapter和ThrowsAdviceAdapter,源代码如下
    在这里插入图片描述
public interface AdvisorAdapter { 
    // 判断此适配器是否支持特定的Advice
    boolean supportsAdvice(Advice var1);
    //将一个Advisor适配成MethodInterceptor
    MethodInterceptor getInterceptor(Advisor var1);
}
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { 
    MethodBeforeAdviceAdapter() { 
    }

    public boolean supportsAdvice(Advice advice) { 
        return advice instanceof MethodBeforeAdvice;
    }

    public MethodInterceptor getInterceptor(Advisor advisor) { 
        MethodBeforeAdvice advice = (MethodBeforeAdvice)advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }
}

Spring会根据不同的AOP配置来使用对应的Advice,与策略模式不同的是,一个方法可以同时拥有多个Advice

  • 2.在DispatcherServlet的doDispatch()方法中也有使用,在doDispatch()方法中调用了getHandlerAdapter()方法.源码如下
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { 
        if (this.handlerAdapters != null) { 
            Iterator var2 = this.handlerAdapters.iterator();

            while(var2.hasNext()) { 
                HandlerAdapter adapter = (HandlerAdapter)var2.next();
                //判断是否兼容
                if (adapter.supports(handler)) { 
                    return adapter;
                }
            }
        }

        throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }

适配器模式的优缺点
优点:

  • 1.提高类的透明性和复用性,现有的类会被复用但不需要改变
  • 2.目标类和适配器类解耦,可以提供程序的可扩展性
  • 3.在很多业务场景中符合开闭原则

缺点:

  • 1.在适配器代码编写的过程中需要进行全面考虑,可能会增加系统的复杂度
  • 2.增加了代码的阅读难度,降低了代码的可读性,过多使用适配器会使系统的代码变得凌乱

9. 装饰器模式

装饰器模式(Decorator Pattern)是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的方案(扩展原有对象的功能),属于结构性模式,装饰者模式适用于以下场景

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

此处以卖煎饼为示例

public class Battercake { 
    protected String product() { 
        return "煎饼";
    }
    protected int getPrice() { 
        return 5;
    }
    @Override
    public String toString() { 
        return product() + ",总价格:" + getPrice();
    }
}
//加一个鸡蛋
public class BattercakeWithEgg extends  Battercake { 
    @Override
    protected String product() { 
        return super.product()+"加1个鸡蛋";
    }
    @Override
    protected int getPrice() { 
        return super.getPrice()+1;
    }
}
//加一个鸡蛋和一个香肠
public class BattercakeWithEggAndSausage extends BattercakeWithEgg { 
    @Override
    protected String product() { 
        return super.product()+"加1个香肠";
    }
    @Override
    protected int getPrice() { 
        return super.getPrice()+2;
    }
}
public class Main { 
    public static void main(String[] args) { 
        Battercake battercake = new Battercake();
        System.out.println(battercake);
        Battercake battercakeWithEgg = new BattercakeWithEgg();
        System.out.println(battercakeWithEgg);
        Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
        System.out.println(battercakeWithEggAndSausage);
    }
}

在这里插入图片描述

装饰器模式和适配器模式对比
装饰器模式和适配器模式都是(Wraooer Pattern),装饰器模式也是一种特殊的代理模式,二者对比如下
列名 装饰器模式 适配器模式 形式 是一种特别的适配器模式 没有层级关系,装饰器模式有 定义 装饰者和被装饰者实现同一个接口,主要目的是扩展之后依旧保留OOP关系 适配器和被适配者没有必然的联系,通常采用继承或者代理的形式进行包装 关系 满足 is-a的关系 满足has-a的关系 功能 注重覆盖,扩展 注重兼容,转换 设计 前置考虑 后置考虑

  • is-a : 表示类与类之间的继承关系、接口与接口之间的继承的关系以及类对接口实现的关系
  • has-a: 是关联关系的一种,是整体和部分(通常为一个私有的变量)之间的关系,并且代表的整体对象负责构建和销毁代表部分

装饰器模式在源码中的应用
在JDK中体现最多的就是I/O相关的类,如BufferedReader,InputStream,OutputStream
如下图,FilterInputStream被3个子类引用了.
在这里插入图片描述

在Spring中的TransactionAwareCacheDecotator类中有被使用,这个类主要用于处理事务缓存的,源码如下

public class TransactionAwareCacheDecorator implements Cache { 
    private final Cache targetCache;
    public TransactionAwareCacheDecorator(Cache targetCache) { 
        Assert.notNull(targetCache, "Target Cache must not be null");
        this.targetCache = targetCache;
    }
}

TransactionAwareCacheDecorator就是对Cache的一个包装.

装饰器模式的优缺点
优点:

  • 1.装饰器模式是继承的有力补充,且比继承灵活,可以在不改变原有对象的情况下动态给一个对象扩展功能,即插即用.
  • 2.使用不同的装饰类及这些装饰类的排列组合,可以实现不同的效果
  • 3.装饰器模式完全符合开闭原则

缺点:

  • 1.会出现更多的代码,更多的类,增加程序的复杂性
  • 2.多层装饰会更复杂

10.观察者模式

观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有的依赖者(观察者)都会受到通知并更新,属于行为者模式,观察者模式有时候也叫发布订阅模式,例如我们生活中的邮件通知,桌面程序的事件响应等.

//监听器的一种包装,标注事件源格式的定义
public class Event { 
    //事件源,事件是由谁发起的,保存起来
    private Object source;
    //事件触发,要通知谁
    private Object target;
    //事件触发,要做什么动作,回调
    private Method callback;
    //事件的名称,触发的是什么事件
    private String trigger;
    //事件触发的时间
    private long time;
    public Event(Object target, Method callback) { 
        this.target = target;
        this.callback = callback;
    }
    public Event setSource(Object source) { 
        this.source = source;
        return this;
    }
    public Event setTrigger(String trigger) { 
        this.trigger = trigger;
        return this;
    }
    public Event setTime(long time) { 
        this.time = time;
        return this;
    }
}
 //监听器,它就是观察者的桥梁
public class EventListenter { 
    protected Map<String, Event> events = new HashMap<>();
    //通过事件名称和一个目标对象来触发事件
    public void addLisenter(String eventType, Object target) { 
        try { 
            this.addLisenter(eventType, target, target.getClass().getMethod(eventType, Event.class));
        } catch (Exception e) { 
            e.printStackTrace();
        }
    }
    public void addLisenter(String eventType, Object target, Method callback) { 
        //注册事件
        events.put(eventType, new Event(target, callback));
    }
    //触发
    private void trigger(Event event) { 
        event.setSource(this);
        event.setTime(System.currentTimeMillis());
        try { 
            //发起回调
            if (event.getCallback() != null) { 
                //用反射调用它的回调函数
                event.getCallback().invoke(event.getTarget(), event);
            }
        } catch (Exception e) { 
            e.printStackTrace();
        }
    }
    protected void trigger(String trigger){ 
        if(!this.events.containsKey(trigger)){ return;}
        trigger(this.events.get(trigger).setTrigger(trigger));
    }
}
//定义事件类型
public interface EventType { 
    String CLICK="click";
}
/** * @author Dominick Li * @CreateTime 2021/1/19 22:37 * @description 回调函数类 **/
public class MouseEventCallback { 
    public void click(Event e){ 
        System.out.println("==============触发鼠标点击事件===============\n"+e);
    }
}
//被观察者
public class Mouse extends EventListenter { 
    public void click(){ 
        System.out.println("调用单机事件");
        this.trigger(EventType.CLICK);
    }
}

public class Main { 
    public static void main(String[] args) { 
        MouseEventCallback callback=new MouseEventCallback();
        //注册事件
        Mouse mouse=new Mouse();
        mouse.addLisenter(EventType.CLICK,callback);
        //调用方法
        mouse.click();
    }
}

观察者模式在源码中的应用
Spring中的ContextLoaderListener类实现了ServletContextListener接口,ServletContextListener又继承了EventListener接口

public class ContextLoaderListener extends ContextLoader implements ServletContextListener { 
    public ContextLoaderListener() { 
    }
    public ContextLoaderListener(WebApplicationContext context) { 
        super(context);
    }
    public void contextInitialized(ServletContextEvent event) { 
        this.initWebApplicationContext(event.getServletContext());
    }
    public void contextDestroyed(ServletContextEvent event) { 
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}
public interface ServletContextListener extends EventListener { 
    default void contextInitialized(ServletContextEvent sce) { 
    }
    default void contextDestroyed(ServletContextEvent sce) { 
    }
}
public interface EventListener { 
}

基于Guava API轻松实现观察者模式

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>
public class GuavaEvent { 
    @Subscribe
    public void subscribe(String str){ 
        System.out.println("执行subscribe方法,传入的参数是"+str);
    }
}
public class Main { 
    public static void main(String[] args) { 
        EventBus eventBus=new EventBus();
        GuavaEvent guavaEvent=new GuavaEvent();
        eventBus.register(guavaEvent);
        eventBus.post("我喜欢你");
    }
}

观察者模式的优缺点
优点

  • 1.在观察者和被观察者之间建立了一个抽象的耦合
  • 2.观察者模式支持广播通信

缺点

  • 1.观察者之间有过多的细节依赖,时间消耗多,程序的复杂性更高
  • 2.使用不当会出现循环调用

三.GoF23种设计模式简介

分类 设计模式 创建型 工厂方法模式,抽象工厂模式,建造者模式,原型模式,单例模式 结构型 适配器模式,桥接模式,组合模式,装饰者模式,门面模式,享元模式,代理模式 行为型 解释器模式,模板方法模式(模板模式),责任链模式,命令模式,迭代器模式,调节者模式,备忘录模式,观察者模式,状态模式,策略模式,访问者模式.

设计模式直接的关联关系
在这里插入图片描述

模式组合 描述 单例模式和工厂模式 在实际使用中,会把工厂类设置为单例模式 策略模式和工厂模式 工厂模式包含工厂方法模式和抽象工厂模式,是创建型模式,策略模式属于行为者模式,工厂模式的主要目的是封装好创建逻辑,策略模式接受工厂创建好的对象,从而实现不同的行为 策略模式和委派模式 策略模式是委派模式内部的一种实现方式,测试模式关注结果是否能相互替代,委派模式更关注分发和调度的过程 模板方法模式和工厂方法模式 工厂方法模式是模板方法模式的一种特殊实现,对于工厂方法模式的create()方法而言,工厂方法模式相当于只有一个步骤的模板方法模式,这个步骤交给子类实现。 模式方法模式和策略模式 模板方法模式和策略模式都有封装算法,策略模式使不同算法可以相互替换,且不影响客户端应用,使用模板方法模式针对定义一个算法的流程,将一些有细微差异的部分交给子类实现,模板方法模式不能改变算法流程,策略模式可以 装饰者模式和代理模式 装饰者模式的关注点在于给对象动态添加方法,而代理模式根加注重控制对象的访问,代理模式通常会在代理类中创建被代理类对象的实例,二装饰者模式通常会把装饰者作为构造参数 装饰者模式和适配器模式 装饰者模式和适配器模式都属于包装模式 (Wrapper Pattern),装饰者模式可以试下与被装饰者相同的接口,或者继承被装饰者作为它的子类,而适配器和被适配器着都可以实现不同的接口. 适配器模式和静态代理模式 适配器模式可以结合静态代理模式来实现,保存被适配对象的引用 适配器模式和策略模式 在业务复杂的情况下可以用策略模式优化器适配器模式

相关文章