23种设计模式学习笔记(5)

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 23种设计模式学习笔记(5),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

23种设计模式学习笔记(1)
https://blog.csdn.net/qq_51495235/article/details/115358623

23种设计模式学习笔记(2)
https://blog.csdn.net/qq_51495235/article/details/115358846

23种设计模式学习笔记(3)
https://blog.csdn.net/qq_51495235/article/details/115359128

23种设计模式学习笔记(4)
https://blog.csdn.net/qq_51495235/article/details/115359246

17,代理模式

17.1 代理模式的定义和特点

代理模式的定义:**由于某些原因需要给某对象提供一个代理以控制对该对象的访问。**这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

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

17.2 代理模式的结构与实现

17.2.1 代理模式的结构

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

根据代理的创建时期,代理模式分为静态代理和动态代理。

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态:在程序运行时,运用反射机制动态创建而成

17.2.2 代码实现

关系类图

23种设计模式学习笔记(5)

17.2.2.1静态代理

SellTickets

package com.zhuang.proxy.static_proxy;

/**
 * @Classname SellTickets
 * @Description 卖票接口
 * @Date 2021/3/26 8:01
 * @Created by dell
 */

public interface SellTickets {
    void sell();
}

Transition

package com.zhuang.proxy.static_proxy;

/**
 * @Classname Transition
 * @Description 火车站,具有卖票功能,实现接口
 * @Date 2021/3/26 8:01
 * @Created by dell
 */

public class Transition implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyPoint

package com.zhuang.proxy.static_proxy;

/**
 * @Classname ProxyPoint
 * @Description 代售点 实现接口
 * @Date 2021/3/26 8:02
 * @Created by dell
 */

public class ProxyPoint implements SellTickets {
    private Transition transition = new Transition();

    @Override
    public void sell() {
        System.out.println("代售点收取服务费");
        transition.sell();
    }
}

Client

package com.zhuang.proxy.static_proxy;

/**
 * @Classname Client
 * @Description 静态代理客户端测试类
 * @Date 2021/3/26 8:02
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
    }
}

23种设计模式学习笔记(5)

从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强

17.2.2.2 JDK动态代理

使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

SellTickets

package com.zhuang.proxy.jdk_proxy;

/**
 * @Classname SellTickets
 * @Description 卖票接口
 * @Date 2021/3/26 8:01
 * @Created by dell
 */

public interface SellTickets {
    void sell();
}

Transition

package com.zhuang.proxy.jdk_proxy;

/**
 * @Classname Transition
 * @Description 火车站,具有卖票功能,实现接口
 * @Date 2021/3/26 8:01
 * @Created by dell
 */

public class Transition implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory

package com.zhuang.proxy.jdk_proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Classname ProxyFactory
 * @Description 代理工厂 用来创建代理对象
 * @Date 2021/3/26 8:11
 * @Created by dell
 */

public class ProxyFactory {

    private Transition transition = new Transition();

    public SellTickets getProxyObject() {
        //使用Proxy获取代理对象
        /* newProxyInstance
        ClassLoader loader, 类加载器
        Class<?>[] interfaces, 接口
        InvocationHandler h 方法

        invoke 方法参数说明
        proxy 代理对象
        method 对应于在代理对象调用的接口方法的Method实例
        args 代理对象调用接口方法时传递的实际参数
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(transition.getClass().getClassLoader(),
                transition.getClass().getInterfaces()
                , new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点收取服务费(JDK动态代理方式)");
                        //执行真实对象
                        Object result = method.invoke(transition, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}

Client

package com.zhuang.proxy.jdk_proxy;

/**
 * @Classname Client
 * @Description JDK动态代理 测试类
 * @Date 2021/3/26 8:20
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        //获取代理对象
        ProxyFactory factory = new ProxyFactory();

        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

23种设计模式学习笔记(5)

使用了动态代理,我们思考下面问题:

  • ProxyFactory是代理类吗?

    ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:

    package com.sun.proxy;
    
    import com.itheima.proxy.dynamic.jdk.SellTickets;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                return;
            }
            catch (NoSuchMethodException noSuchMethodException) {
                throw new NoSuchMethodError(noSuchMethodException.getMessage());
            }
            catch (ClassNotFoundException classNotFoundException) {
                throw new NoClassDefFoundError(classNotFoundException.getMessage());
            }
        }
    
        public final boolean equals(Object object) {
            try {
                return (Boolean)this.h.invoke(this, m1, new Object[]{object});
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString() {
            try {
                return (String)this.h.invoke(this, m2, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode() {
            try {
                return (Integer)this.h.invoke(this, m0, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void sell() {
            try {
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    

    从上面的类中,我们可以看到以下几个信息:

    • 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
    • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
  • 动态代理的执行流程是什么样?

    下面是摘取的重点代码:

    //程序运行过程中动态生成的代理类
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
        }
    
        public final void sell() {
            this.h.invoke(this, m3, null);
        }
    }
    
    //Java提供的动态代理相关类
    public class Proxy implements java.io.Serializable {
    	protected InvocationHandler h;
    	 
    	protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    }
    
    //代理工厂类
    public class ProxyFactory {
    
        private TrainStation station = new TrainStation();
    
        public SellTickets getProxyObject() {
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
                        
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    
    
    //测试访问类
    public class Client {
        public static void main(String[] args) {
            //获取代理对象
            ProxyFactory factory = new ProxyFactory();
            SellTickets proxyObject = factory.getProxyObject();
            proxyObject.sell();
        }
    }
    

执行流程如下:

  1. 在测试类中通过代理对象调用sell()方法
  2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
  3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
  4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

17.2.2.3 CGLB代理

TrainStation

package com.zhuang.proxy.cglib_proxy;

/**
 * @Classname TrainStation
 * @Description 火车站类
 * @Date 2021/3/26 8:31
 * @Created by dell
 */

public class TrainStation {
    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory

package com.zhuang.proxy.cglib_proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Classname ProxyFactory
 * @Description 代理工厂 实现MethodInterceptor
 * @Date 2021/3/26 8:32
 * @Created by dell
 */

public class ProxyFactory implements MethodInterceptor {

    private TrainStation target = new TrainStation();

    public TrainStation getProxyObject() {
        //创建Enhancer对象  类似于JDK动态代理
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation obj = (TrainStation) enhancer.create();
        return obj;

    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代收点收取一些代理费用(CGLIB动态代理方式)");
        Object result = methodProxy.invokeSuper(o, args);
        return result;
    }
}

Client

package com.zhuang.proxy.cglib_proxy;

/**
 * @Classname Client
 * @Description CGLIB动态代理模式 测试类
 * @Date 2021/3/26 8:42
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = factory.getProxyObject();

        proxyObject.sell();
    }
}

23种设计模式学习笔记(5)

17.2.3 三种代理的对比

  • jdk代理和CGLIB代理

    使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理和静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

17.3 代理模式应用场景

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

18,命令模式

18.1 命令模式的定义和特点

命令(Command)模式的定义如下:**将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。**这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

命令模式的主要优点如下。

  1. 通过引入中间件(抽象接口)降低系统的耦合度。
  2. 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
  3. 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  5. 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

其缺点是:

  1. 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
  2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

18.2 命令模式的结构与实现

18.2.1 命令模式的结构

  1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

18.2.2 代码实现

关系类图

23种设计模式学习笔记(5)

Command

package com.zhuang.command;

/**
 * @Classname Command
 * @Description 抽象命令类
 * @Date 2021/3/27 10:25
 * @Created by dell
 */

public interface Command {
    void execute(); // 只需要定义一个统一的执行方法
}

OrderCommand

package com.zhuang.command;

/**
 * @Classname OrderCommand
 * @Description  具体命令类
 * @Date 2021/3/27 10:25
 * @Created by dell
 */

public class OrderCommand implements Command{

    //持有接受者对象
    private SeniorChef receiver;

    private Order order;

    public OrderCommand(SeniorChef receiver, Order order) {
        this.receiver = receiver;
        this.order = order;
    }

    @Override
    public void execute() {
        System.out.println(order.getDiningTable()+"桌的订单:");
        for (String key : order.getFoodDic().keySet()) {
            receiver.makefood(order.getFoodDic().get(key),key);
        }
        try {
            Thread.sleep(1000); //模拟做饭 睡眠1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(order.getDiningTable()+"桌的饭弄好了");
    }
}

Order

package com.zhuang.command;

import java.util.HashMap;
import java.util.Map;

/**
 * @Classname Order
 * @Description 订单类
 * @Date 2021/3/27 10:34
 * @Created by dell
 */

public class Order {
    // 餐桌号码
    private int diningTable;

    //用来存储餐名并记录
    private Map<String, Integer> foodDic = new HashMap<String, Integer>();

    public int getDiningTable() {
        return diningTable;
    }

    public void setDiningTable(int diningTable) {
        this.diningTable = diningTable;
    }

    public Map<String, Integer> getFoodDic() {
        return foodDic;
    }

    public void setFoodDic(String name, int num) {
        foodDic.put(name, num);

    }
}

SeniorChef

package com.zhuang.command;

/**
 * @Classname SeniorChef
 * @Description 厨师类
 * @Date 2021/3/27 10:27
 * @Created by dell
 */

public class SeniorChef {
    //大厨师类 是命令的Receiver

    public void makefood(int num, String foodName) {
        System.out.println(num + "份" + foodName);
    }

}

Waitor

package com.zhuang.command;

import java.util.ArrayList;

/**
 * @Classname Waitor
 * @Description 服务员类
 * @Date 2021/3/27 10:30
 * @Created by dell
 */

public class Waitor {

    //可以持有很多命令对象
    private ArrayList<Command> commands;

    public Waitor() {
        commands = new ArrayList<Command>();
    }

    public void setCommands(Command cmd) {
        commands.add(cmd);
    }

    //发出命令 订单来了 大厨师开始执行命令
    public void orderUp() {
        System.out.println("来活了...");
        for (int i = 0; i < commands.size(); i++) {
            Command cmd = commands.get(i);
            if (cmd != null) {
                cmd.execute();
            }
        }
    }
}

Client

package com.zhuang.command;

/**
 * @Classname Client
 * @Description 命令模式 测试类
 * @Date 2021/3/27 10:44
 * @Created by dell
 */

public class Client {

    public static void main(String[] args) {
        //创建order
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.getFoodDic().put("西红柿炒鸡蛋", 1);
        order1.getFoodDic().put("罐装可乐", 2);

        Order order2 = new Order();
        order2.setDiningTable(2);
        order2.getFoodDic().put("酸溜土豆丝", 1);
        order2.getFoodDic().put("王老吉", 1);

        //创建接受者
        SeniorChef receiver = new SeniorChef();
        //将订单和接受者封装成命令对象
        OrderCommand cmd1 = new OrderCommand(receiver, order1);
        OrderCommand cmd2 = new OrderCommand(receiver, order2);
        //创建调用者 waitor
        Waitor invoke = new Waitor();
        invoke.setCommands(cmd1);
        invoke.setCommands(cmd2);

        //将订单给柜台 呼叫厨师
        invoke.orderUp();


    }
}

23种设计模式学习笔记(5)

18.3 命令模式应用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

18.4 JDK源码解析

Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法

//命令接口(抽象命令角色)
public interface Runnable {
	public abstract void run();
}

//调用者
public class Thread implements Runnable {
    private Runnable target;
    
    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
    
    private native void start0();
}

会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。

/**
 * jdk Runnable 命令模式
 *		TurnOffThread : 属于具体
 */
public class TurnOffThread implements Runnable{
     private Receiver receiver;
    
     public TurnOffThread(Receiver receiver) {
     	this.receiver = receiver;
     }
     public void run() {
     	receiver.turnOFF();
     }
}
/**
 * 测试类
 */
public class Demo {
     public static void main(String[] args) {
         Receiver receiver = new Receiver();
         TurnOffThread turnOffThread = new TurnOffThread(receiver);
         Thread thread = new Thread(turnOffThread);
         thread.start();
     }
}

19,访问者模式

19.1 访问者模式的定义和特点

访问者(Visitor)模式的定义:**将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。**它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下。

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

19.2 访问者模式的结构与实现

19.2.1 访问者模式的结构

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

19.2.2 代码实现

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人、其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗、宠物猫
  • 结构对象角色:主人家

23种设计模式学习笔记(5)

Person

package com.zhuang.visitor;

/**
 * @Classname Person
 * @Description 抽象访问者接口
 * @Date 2021/3/27 16:48
 * @Created by dell
 */

public interface Person {
    //喂宠物狗
    void feed(Dog dog);

    //喂宠物猫
    void feed(Cat cat);
}

Owner

package com.zhuang.visitor;

/**
 * @Classname Owner
 * @Description 具体访问者角色 主人类
 * @Date 2021/3/27 16:49
 * @Created by dell
 */

public class Owner implements Person {
    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂食宠物狗...");
    }

    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂食宠物猫...");
    }
}

Someone

package com.zhuang.visitor;

/**
 * @Classname Someone
 * @Description 具体访问者角色 其他人类
 * @Date 2021/3/27 16:49
 * @Created by dell
 */

public class Someone implements Person {
    @Override
    public void feed(Dog dog) {
        System.out.println("其他人喂食宠物狗...");
    }

    @Override
    public void feed(Cat cat) {
        System.out.println("其他人喂食宠物猫...");
    }
}

Animal

package com.zhuang.visitor;

/**
 * @Classname Animal
 * @Description 定义抽象节点
 * @Date 2021/3/27 16:50
 * @Created by dell
 */

public interface Animal {
    void accept(Person person);
}

Dog

package com.zhuang.visitor;

/**
 * @Classname Dog
 * @Description 具体节点 实现Animal接口
 * @Date 2021/3/27 16:48
 * @Created by dell
 */

public class Dog implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("真香~,汪汪汪!!!");
    }
}

Cat

package com.zhuang.visitor;

/**
 * @Classname Cat
 * @Description 用一句话描述类的作用
 * @Date 2021/3/27 16:49
 * @Created by dell
 */

public class Cat implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("真香~,喵喵喵!!!");
    }
}

Home

package com.zhuang.visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * @Classname Home
 * @Description 定义对象结构  主人的家
 * @Date 2021/3/27 16:50
 * @Created by dell
 */

public class Home {
    private List<Animal> nodeList = new ArrayList<Animal>();

    //添加方法
    public void add(Animal animal) {
        nodeList.add(animal);
    }

    public void aciton(Person person) {
        for (Animal node : nodeList) {
            node.accept(person);
        }
    }
}

Client

package com.zhuang.visitor;

/**
 * @Classname Client
 * @Description 访问者模式 测试类
 * @Date 2021/3/27 16:50
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Dog());
        home.add(new Cat());

        Owner owner = new Owner();
        home.aciton(owner);
        System.out.println("===========================");
        Someone someone = new Someone();
        home.aciton(someone);
    }
}

23种设计模式学习笔记(5)

19.3 扩展

访问者模式用到了一种双分派的技术。

1,分派:

变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap() ,map变量的静态类型是 Map ,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。

静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。

动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。

2,动态分派:

通过方法的重写支持动态分派。

public class Animal {
    public void execute() {
        System.out.println("Animal");
    }
}

public class Dog extends Animal {
    @Override
    public void execute() {
        System.out.println("我是狗...");
    }
}

public class Cat extends Animal {
     @Override
    public void execute() {
        System.out.println("我是猫...");
    }
}

public class Client {
   	public static void main(String[] args) {
        Animal a = new Dog();
        a.execute();
        
        Animal a1 = new Cat();
        a1.execute();
    }
}

23种设计模式学习笔记(5)

上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。

Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。

3,静态分派:

通过方法重载支持静态分派。

public class Animal {
}

public class Dog extends Animal {
}

public class Cat extends Animal {
}

public class Execute {
    public void execute(Animal a) {
        System.out.println("Animal");
    }

    public void execute(Dog d) {
        System.out.println("我是狗...");
    }

    public void execute(Cat c) {
        System.out.println("我是猫...");
    }
}

public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal a1 = new Dog();
        Animal a2 = new Cat();

        Execute exe = new Execute();
        exe.execute(a);
        exe.execute(a1);
        exe.execute(a2);
    }
}

运行结果:

23种设计模式学习笔记(5)

这个结果可能出乎一些人的意料了,为什么呢?

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。

4,双分派:

所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。

public class Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Dog extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Cat extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Execute {
    public void execute(Animal a) {
        System.out.println("animal");
    }

    public void execute(Dog d) {
        System.out.println("我是狗...");
    }

    public void execute(Cat c) {
        System.out.println("我是猫...");
    }
}

public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal d = new Dog();
        Animal c = new Cat();

        Execute exe = new Execute();
        a.accept(exe);
        d.accept(exe);
        c.accept(exe);
    }
}

在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。

说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。

运行结果如下:

23种设计模式学习笔记(5)

双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。

20,迭代器模式

20.1 迭代器模式的定义和特点

迭代器(Iterator)模式的定义:**提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。**迭代器模式是一种对象行为型模式,其主要优点如下

  1. 访问一个聚合对象的内容而无须暴露它的内部表示。
  2. 遍历任务交由迭代器完成,这简化了聚合类。
  3. 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
  4. 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  5. 封装性良好,为遍历不同的聚合结构提供一个统一的接口。

其主要缺点是:

  • 增加了类的个数,这在一定程度上增加了系统的复杂性。

20.2 迭代器模式的结构与实现

20.2.1 迭代器模式的结构

  1. 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  2. 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  3. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  4. 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

20.2.2 代码实现

定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现

关系类图

23种设计模式学习笔记(5)

Student

package com.zhuang.Iterator;

/**
 * @Classname Student
 * @Description 学生类
 * @Date 2021/3/28 12:47
 * @Created by dell
 */

public class Student {
    private String name;
    private String number;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public Student() {
    }

    public Student(String name, String number) {
        this.name = name;
        this.number = number;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", number='" + number + '\'' +
                '}';
    }
}

StudentIterator

package com.zhuang.Iterator;

/**
 * @Classname StudentIterator
 * @Description 迭代器接口
 * @Date 2021/3/28 12:43
 * @Created by dell
 */

public interface StudentIterator {
    boolean hasNext();

    Student next();
}

StudentIteratorImpl

package com.zhuang.Iterator;

import java.util.List;

/**
 * @Classname StudentIteratorImpl
 * @Description 具体的迭代器类
 * @Date 2021/3/28 12:43
 * @Created by dell
 */

public class StudentIteratorImpl implements StudentIterator {

    private List<Student> list;
    private int position = 0;

    public StudentIteratorImpl(List<Student> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return position < list.size();
    }

    @Override
    public Student next() {
        Student currentStudent = list.get(position);
        position++;
        return currentStudent;
    }
}

StudentAggregate

package com.zhuang.Iterator;

/**
 * @Classname StudentAggregate
 * @Description 抽象容器类
 * @Date 2021/3/28 12:44
 * @Created by dell
 */

public interface StudentAggregate {
    //添加学生的功能
    void addStudent(Student student);

    //删除学生的功能
    void removeStudent(Student student);

    //获取迭代器功能的对象
    StudentIterator getStudentIterator();
}

StudentAggregateImpl

package com.zhuang.Iterator;

import java.util.ArrayList;
import java.util.List;

/**
 * @Classname StudentAggregateImpl
 * @Description 具体的容器类
 * @Date 2021/3/28 12:44
 * @Created by dell
 */

public class StudentAggregateImpl implements StudentAggregate {

    private List<Student> list = new ArrayList<Student>();

    @Override
    public void addStudent(Student student) {
        this.list.add(student);
    }

    @Override
    public void removeStudent(Student student) {
        this.list.remove(student);
    }

    @Override
    public StudentIterator getStudentIterator() {
        return new StudentIteratorImpl(list);
    }
}

Client

package com.zhuang.Iterator;

/**
 * @Classname Client
 * @Description 迭代器模式 测试类
 * @Date 2021/3/28 12:54
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        StudentAggregateImpl aggregate = new StudentAggregateImpl();
        //添加元素
        aggregate.addStudent(new Student("张三", "001"));
        aggregate.addStudent(new Student("李四", "002"));
        aggregate.addStudent(new Student("王五", "003"));
        aggregate.addStudent(new Student("赵六", "004"));
        aggregate.addStudent(new Student("田七", "005"));

        //遍历聚合对象
        StudentIterator iterator = aggregate.getStudentIterator();
        //遍历
        while (iterator.hasNext()) {
            Student student = iterator.next();
            System.out.println(student);
        }

    }
}

23种设计模式学习笔记(5)

20.3 迭代器模式的应用场景

  • 当需要为聚合对象提供多种遍历方式时。
  • 当需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

20.4 JDK源码解析

迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。

List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明

  • List:抽象聚合类
  • ArrayList:具体的聚合类
  • Iterator:抽象迭代器
  • list.iterator():返回的是实现了 Iterator 接口的具体迭代器对象

具体的来看看 ArrayList的代码实现

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    public Iterator<E> iterator() {
        return new Itr();
    }
    
    private class Itr implements Iterator<E> {
        int cursor;       // 下一个要返回元素的索引
        int lastRet = -1; // 上一个返回元素的索引
        int expectedModCount = modCount;

        Itr() {}
		
        //判断是否还有元素
        public boolean hasNext() {
            return cursor != size;
        }

        //获取下一个元素
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        ...
}

这部分代码还是比较简单,大致就是在 iterator 方法中返回了一个实例化的 Iterator 对象。Itr是一个内部类,它实现了 Iterator 接口并重写了其中的抽象方法。

注意:

当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable并实现其中的iterator()方法使其返回一个 java.util.Iterator 的实现类就可以了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/140737.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!