• 二十三种设计模式(待更)


    二十三种设计模式

    资料来源于老师讲解以及大佬的设计模式仓库 zhengqingya

    结构型

    将对象和类按某种布局组成更大的结构,并同时保持结构的灵活和⾼效。

    1.适配器

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LcDyM5V5-1688367897990)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E9%80%82%E9%85%8D%E5%99%A8.png)]

    适配器就是将原先无法直接使用的某个接口或者类通过适配器模式转换为可以使用的接口或者类。将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式的好处就是可以再不改变原有代码的基础之上也可以实现相关的功能。

    角色:

    1. 目标接口(Target):客户所期待的接口。可以是具体类 或 抽象的类,也可以是接口
    2. 需要适配的类(Adaptee)
    3. 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口

    区别:

    • 类适配器:单继承,一次最多只能适配一个适配者类
    • 对象适配器:可以把多个不同的适配者适配到同一个目标

    适配器模式分为:类适配器、对象适配器、接口适配器。

    tips: 推荐使用对象适配器

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zogeHOol-1688367897991)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/Adapter.jpg)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EN7k3cTM-1688367897992)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/Adapter_classModel.jpg)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W0wDHFt7-1688367897992)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/seq_Adapter.jpg)]

    1.1 接口适配器

    接口中有很多的抽象方法,但是再使用接口的时候我们只关注其中某一个方法,其他方法我们不需要,但是因为接口的特性,如果我们要使用这个接口,必须重写接口中所有方法。

    创建一个接口的适配器,接口的适配器是一个Java类,适配器需要将接口的所有抽象方法给重写了,但是重写之后只做空实现,后期如果使用接口,只需要继承适配器类。

    Java中GUI中有很多接口适配器,MouseAdpter

    代码示例:

    package com.xsuek.adpter;
    /**
     * 二十三种设计模式之一,适配器模式
     * 在某些情况下Java类只想重写使用接口中的某个方法  而不是所有的抽象方法
     * 但是因为接口的特性  子类实现接口  必须重写所有方法
     * 
     * 适配器模式:一个适配类由这个适配类是实现接口,并且重写了所有的方法,但是所有的方法都是空实现。
     * 如果某个类想要去重写使用接口中的某个方法,就不要直接实现接口,而是继承适配器类。
     * @author lenovo
     *
     */
    public interface MouseInter {
    	void mouseClick();
    	int mouseMove();
    	String mouseEnter();
    	void mouseleave();
    }
    class MouseAdpter implements MouseInter{
    	@Override
    	public void mouseClick() {			
    	}
    	@Override
    	public int mouseMove() {
    		return 0;		
    	}
    	@Override
    	public String mouseEnter() {		return null;		
    	}
    	@Override
    	public void mouseleave() {
            // TODO Auto-generated method stub		
    	}		
    }
    class Button extends MouseAdpter{		
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    1.2 类适配器

    有一个电压220V实现手机充电的效果电源适配器将220V的电压转换成为可以充电的东西。

    让适配器类继承被适配器

    package com.sxuek.designmodel;
    
    public class U {
    	public void out() {
    		System.out.println("220V");
    	}
    }
    interface Chongdian{
    	void chongdian();
    }
    class ChrageAapter extends U implements Chongdian{
    	@Override
    	public void chongdian(){
    			out();
    			//xxxxxxx
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1.3 对象适配器

    让被适配的类称为适配器的一个属性。

    代码示例:

    public class AC220 {
        public int outputAC220V() {
            int output = 220;
            System.out.println("输出电压:" + output + "V");
            return output;
        }
    }
    public interface DC5 {
        int outputDC5V();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    public class PowerAdapter implements DC5 {
    
        private AC220 ac220;
    
        public PowerAdapter(AC220 ac220) {
            this.ac220 = ac220;
        }
    
        @Override
        public int outputDC5V() {
            int adapterInput = ac220.outputAC220V();
            int adapterOutput = adapterInput / 44;
            System.out.println("输入AC" + adapterInput + "输出DC" + adapterOutput);
            return adapterOutput;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class Test {
        public static void main(String[] args) {
            DC5 adapter = new PowerAdapter(new AC220());
    
            adapter.outputDC5V();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.代理模式

    为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

    AOP核心:代理模式

    在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

    通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即 为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机。

    2.1 模式结构
    1. Subject: 抽象主题角色
    2. Proxy: 代理主题角色
    3. RealSubject: 真实主题角色

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X5BsfZAP-1688367897993)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/Proxy.jpg)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDyr4fn8-1688367897994)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/seq_Proxy.jpg)]

    2.1.1 静态代理

    以租房为例,我们一般用租房软件、找中介或者找房东。这里的中介就是代理者。

    首先定义一个提供了租房方法的接口。

    public interface IRentHouse {
        void rentHouse();
    }
    
    • 1
    • 2
    • 3

    定义租房的实现类

    public class RentHouse implements IRentHouse {
        @Override
        public void rentHouse() {
            System.out.println("租了一间房子。。。");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我要租房,房源都在中介手中,所以找中介

    public class IntermediaryProxy implements IRentHouse {
    
        private IRentHouse rentHouse;
    
        public IntermediaryProxy(IRentHouse irentHouse){
            rentHouse = irentHouse;
        }
    
        @Override
        public void rentHouse() {
            System.out.println("交中介费");
            rentHouse.rentHouse();
            System.out.println("中介负责维修管理");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里中介也实现了租房的接口。

    再main方法中测试

    public class Main {
    
        public static void main(String[] args){
            //定义租房
            IRentHouse rentHouse = new RentHouse();
            //定义中介
            IRentHouse intermediary = new IntermediaryProxy(rentHouse);
            //中介租房
            intermediary.rentHouse();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    返回信息

    交中介费
    租了一间房子。。。
    中介负责维修管理
    
    • 1
    • 2
    • 3

    这就是静态代理,因为中介这个代理类已经事先写好了,只负责代理租房业务

    2.1.2.强制代理

    如果我们直接找房东要租房,房东会说我把房子委托给中介了,你找中介去租吧。这样我们就又要交一部分中介费了,真坑。

    来看代码如何实现,定义一个租房接口,增加一个方法。

    public interface IRentHouse {
        void rentHouse();
        IRentHouse getProxy();
    }
    
    • 1
    • 2
    • 3
    • 4

    这时中介的方法也稍微做一下修改

    public class IntermediaryProxy implements IRentHouse {
    
        private IRentHouse rentHouse;
    
        public IntermediaryProxy(IRentHouse irentHouse){
            rentHouse = irentHouse;
        }
    
        @Override
        public void rentHouse() {
            rentHouse.rentHouse();
        }
    
        @Override
        public IRentHouse getProxy() {
            return this;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    其中的getProxy()方法返回中介的代理类对象

    我们再来看房东是如何实现租房:

    public class LandLord implements IRentHouse {
    
        private IRentHouse iRentHouse = null;
    
        @Override
        public void rentHouse() {
            if (isProxy()){
                System.out.println("租了一间房子。。。");
            }else {
                System.out.println("请找中介");
            }
        }
    
        @Override
        public IRentHouse getProxy() {
            iRentHouse = new IntermediaryProxy(this);
            return iRentHouse;
        }
    
        /**
         * 校验是否是代理访问
         * @return
         */
        private boolean isProxy(){
            if(this.iRentHouse == null){
                return false;
            }else{
                return true;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    房东的getProxy方法返回的是代理类,然后判断租房方法的调用者是否是中介,不是中介就不租房。

    main方法测试:

    public static void main(String[] args){
    
            IRentHouse iRentHosue = new LandLord();
            //租客找房东租房
            iRentHouse.rentHouse();
            //找中介租房
            IRentHouse rentHouse = iRentHouse.getProxy();
            rentHouse.rentHouse();
    
    
        }
    
    }
    请找中介
    租了一间房子。。。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    看,这样就是强制你使用代理,如果不是代理就没法访问。

    2.1.3 动态代理

    我们知道现在的中介不仅仅是有租房业务,同时还有卖房、家政、维修等得业务,只是我们就不能对每一个业务都增加一个代理,就要提供通用的代理方法,这就要通过动态代理来实现了。

    中介的代理方法做了一下修改

    public class IntermediaryProxy implements InvocationHandler {
    
    
        private Object obj;
    
        public IntermediaryProxy(Object object){
            obj = object;
        }
    
        /**
         * 调用被代理的方法
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(this.obj, args);
            return result;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里实现InvocationHandler接口,此接口是JDK提供的动态代理接口,对被代理的方法提供代理。其中invoke方法是接口InvocationHandler定义必须实现的, 它完成对真实方法的调用。动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理就会实现接口下所有的方法。通过 InvocationHandler接口, 所有方法都由该Handler来进行处理, 即所有被代理的方法都由 InvocationHandler接管实际的处理任务。

    这里增加一个卖房的业务,代码和租房代码类似。

    main方法测试:

    public static void main(String[] args){
    
        IRentHouse rentHouse = new RentHouse();
        //定义一个handler
        InvocationHandler handler = new IntermediaryProxy(rentHouse);
        //获得类的class loader
        ClassLoader cl = rentHouse.getClass().getClassLoader();
        //动态产生一个代理者
        IRentHouse proxy = (IRentHouse) Proxy.newProxyInstance(cl, new Class[]{IRentHouse.class}, handler);
        proxy.rentHouse();
    
        ISellHouse sellHouse = new SellHouse();
        InvocationHandler handler1 = new IntermediaryProxy(sellHouse);
        ClassLoader classLoader = sellHouse.getClass().getClassLoader();
        ISellHouse proxy1 = (ISellHouse) Proxy.newProxyInstance(classLoader, new Class[]{ISellHouse.class}, handler1);
        proxy1.sellHouse();
    
    }
    租了一间房子。。。
    买了一间房子。。。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在main方法中我们用到了Proxy这个类的方法,

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
    
    • 1
    • 2
    • 3

    loder:类加载器,interfaces:代码要用来代理的接口, h:一个 InvocationHandler 对象 。

    InvocationHandler 是一个接口,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

    public interface InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。

    因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

    总结:

    1、静态代理,代理类需要自己编写代码写成。

    2、动态代理,代理类通过 Proxy.newInstance() 方法生成。

    3、JDK实现的代理中不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。CGLib可以不需要接口。

    4、动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。

    创建型

    1.单例模式

    某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。

    单例模式包含如下角色:

    • Singleton:单例

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdXTcoYC-1688367897995)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/Singleton.jpg)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DmwbplFo-1688367897996)(./%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/seq_Singleton.jpg)]

    1.1单例模式的几种实现方式

    单例模式的实现有多种方式,如下所示:

    1.1.1 懒汉式,线程不安全

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    描述: 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
    这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

    实例

    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
      
        public static Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    接下来介绍的几种实现方式都支持多线程,但是在性能上有所差异。

    1.1.2 懒汉式,线程安全

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    描述: 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

    优点:第一次调用才初始化,避免内存浪费。

    缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

    getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

    实例

    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
        public static synchronized Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1.1.3 饿汉式

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    描述: 这种方式比较常用,但容易产生垃圾对象。

    优点:没有加锁,执行效率会提高。

    缺点:类加载时就初始化,浪费内存。

    它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

    实例

    public class Singleton {  
        private static Singleton instance = new Singleton();  
        private Singleton (){}  
        public static Singleton getInstance() {  
        return instance;  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1.1.4 双检锁/双重校验锁(DCL,即 double-checked locking)

    JDK 版本: JDK1.5 起

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度: 较复杂

    描述: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

    getInstance() 的性能对应用程序很关键。

    实例

    public class Singleton {  
        private volatile static Singleton singleton;  
        private Singleton (){}  
        public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1.1.5 登记式/静态内部类

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度: 一般

    描述: 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
    这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

    实例

    public class Singleton {  
        private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
            return SingletonHolder.INSTANCE;  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1.1.6 枚举

    JDK 版本: JDK1.5 起

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    **描述:**这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

    这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

    不能通过 reflection attack 来调用私有构造方法。

    实例

    public enum Singleton {  
        INSTANCE;  
        public void whateverMethod() {  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    经验之谈: 一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

    相关资料

  • 相关阅读:
    2022国产8K摄像机介绍
    TensorFlow 的基本概念和使用场景
    【MySQL】:约束全解析
    shell 编程简记
    使用Tensorboard在 PyTorch 中进行可视化
    k8s笔记资源限制,亲和和性 污点和容忍
    【算法专题突破】滑动窗口 - 水果成篮(13)
    java数据结构与算法刷题-----LeetCode 面试题 16:10. 生存人数
    设计模式-工厂方法模式
    [附源码]Java计算机毕业设计SSM高校国防教育管理系统
  • 原文地址:https://blog.csdn.net/cai_4/article/details/131146903