设计模式-代理模式

news2024/9/22 19:34:34

控制和管理访问

玩过扮白脸,扮黑脸的游戏吗?你是一个白脸,提供很好且很友善的服务,但是你不希望每个人都叫你做事,所以找了黑脸控制对你的访问。这就是代理要做的:控制和管理对象。

监视器编码

需求:糖果机能够获得更好的监控,需要提供一份库存以及机器状态的报告。

先为GumballMachine加上处理位置的支持:

public class GumballMachine{
    //位置用String记录
    String location;
    
    //位置被传入构造器中,然后存到实例变量中
    public GumballMachine(String location,int count){
        //构造器内的其他代码
        
        this.location = location;
    }
    
    //getter方法,获取位置信息
    public String getLocation(){
        return location;
    }
    
}

创建另一个类,GumballMonitor(糖果监视器),以便取得机器的位置、糖果的库存量以及当前机器的状态,并打印成一份报告。

public class GumballMonitor{
    GumballMachine machine;
    
    public GumballMonitor(GumballMachine machine){
        this.machine = machine;
    }
    
    //打印报告方法,将位置,库存,机器状态打印出来
    public void report(){
        System.out.println("Gumball Machine: "+machine.getLocation());
        System.out.println("Current inventory: "+machine.getCount() +" gumballs");
        System.out.println("Current state: "+machine.getState());
    }
}

远程代理

远程代理就好比“远程对象的本地代表”

何谓“远程对象”?这是一种对象,活在不同的java虚拟机(JVM)堆中。可以理解为在不同的地址空间运行的远程对象。

何谓“本地代表”?这是一种可以由本地方法调用的对象,其行为会转发到远程对象中。

image

你的客户对象所做的就像是在做远程方法调用,但其实只是调用本地堆中的“代理”对象上的方法,再由代理处理所有网络通信的底层细节。

远程代理应用

如何创建一个代理,知道如何调用在另一个JVM中的对象的方法?

我们无法取得另一个堆的对象的引用,不能这样写:

Duck d = <另一个堆的对象>

RMI可以让我们找到远程JVM内的对象,并允许我们调用它们的方法。

关于RMI调用和本地(正常)的方法调用,有一个不同点。虽然调用远程方法就如同调用本地方法一样,但是客户辅助对象会通过网络发送方法调用,所以网络和I/O的确是存在的。

image

定义代理模式

远程代理是一般代理模式的一种实现,这个模式的变体很多。

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

在糖果机的例子中,代理控制了对远程对象的访问。代理之所以需要控制访问,是因为我们的客户(监视器)不知道如何和远程对象沟通。从某个方面来看,远程代理控制访问,好帮我们处理网络上的细节。

代理模式有许多变体,而这些变体几乎都和“控制访问”的做法有关:

  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的资源
  • 保护代理基于权限控制对资源的访问

image

代理模式总结

代理模式与装饰者模式的区别是什么?

装饰者模式为对象增加行为,而代理是控制对象的访问

代理模式与适配器模式的区别是什么?

代理和适配器都是挡在其他对象的前面,并负责将请求转发给它们。适配器会改变对象适配的接口,而代理则实现相同的接口

如何让客户使用代理,而不是真正的对象?

常用的技巧是提供一个工厂,实例化并返回主题。因为这是在工厂方法内发生的,我们可以用代理包装主题再返回,而客户不知道也不在乎他使用的是代理还是真东西

保护代理应用

Java在java.lang.reflect包中由自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类

因为实际的代理类是在运行时创建的,我们称这个java技术为:动态代理

我们要利用Java的动态代理创建我们下一个代理实现(保护代理)。但在这之前,先让我们看一下类图,了解下动态代理是怎么一回事,就和真实世界中大多数的事物一样,它和代理模式的传统定义有一点出入。

image

上图所示,因为java已经为你创建了Proxy类,所以你需要有办法来告诉Proxy类你要做什么。你不能像以前一样把代码放在Proxy类中,因为Proxy不是你直接实现的。既然这样的代码不能放在Proxy类中,那么要放在哪里?放在InvocationHandler中。InvacationHandler的工作是响应代理的任何调用。你可以把InvacationHandler想成是代理收到方法调用后,请求做实际工作的对象。

对象村的配对

对乡村实现约会服务系统,每个城镇都配对服务,服务系统涉及一个Person bean,允许设置或取得一个人的信息:

public interface PersonBean{
    String getName();
    String getGender();
    String getInterests();
    int getHotOrNotRating();
    
    void setName(String name);
    void setGender(String gender);
    void setInterests(String interests);
    //需要一个整数作为参数,并将它加入此人的运行平均值中
    void setHotOrNotRating(int rating);
}
public class PersonBeanImpl implements PersonBean{
    String name;
    String gender;
    String interests;
    int rating;
    int ratingCount = 0;
    
    public String getName(){
        return name;
    }
    
    public String getGender(){
        return gender;
    }
    
    public int getHotOrNotRating(){
        if(ratingCount == 0){
            return 0;
        }
        return rating/ratingCount;
    }
    
    public void setName(String name){
        this.name = name;
    }
    
    public void setGender(String gender){
        this.gender = gender;
    }
    
    public void setInterests(String interests){
        this.interests = interests;
    }
    
    public void setHotOrNotRating(int rating){
        this.rating+=rating;
        ratingCoun++;
    }
}

系统不应该允许用户篡改别人的数据,根据我们定义PersonBean的方式,任何客户都可以调用任何方法

这是一个我们可以使用保护代理的绝佳例子。什么事保护代理?这是一种根据访问权限决定客户可否访问对象的代理。比方说,如果你有一个雇员对象:

  • 保护代理允许雇员调用对象上的某些方法
  • 保护代理允许经理可以多调用一些其他的方法,像setSalary()
  • 保护代理允许人力资源雇员调用对象上的所有方法

我们希望顾客可以设置自己的信息,同时又防止他人更改这些信息。HotOrNot评分则相反,你不能更改自己的评分,但是他人可以设置你的评分。

为PersonBean创建动态代理

顾客不可以改变自己的HotOrNot评分,也不可以改变其他顾客的个人信息。要修正这些问题,必须创建两个代理:

  • 访问自己PersonBean对象
  • 访问另一个顾客的PersonBean对象

步骤一:创建两个InvocationHandler

InvocationHandler实现了代理的行为,java负责创建真实代理类和对象。我们只需提供在方法调用发生时知道做什么的handler

image
这里只有一个名为invoke()的方法,不管代理被调用的是何种方法,处理器被调用的一定是invoke()方法。

  • 假设proxy的setHotOrNotRating()方法被调用
proxy.setHotOrNotRating(9);
  • proxy会接着调用invocationHandler的invoke()方法
//Method类是Reflection API的一部分,利用它的getName()方法,我们就可以知道proxy被调用的方法是什么
invoke(Object proxy,Method method,Object[] args)
  • handler决定要如何处置这个请求,转发给RealSubject
//我们调用原始proxy被调用的方法。这个对象在调用时被传给我们,只不过加载调用的是真正的主题(person)
return method.invoke(person,args);

当proxy调用invoke()时,要如何应对?通常,会先检查该方法是否来自proxy,并基于该方法的名称和变量做决定。现在我们就来实现OwnerInvocationHandler,以了解工作机制:

import java.lang.reflect.*;

//所有调用处理器都实现InvocationHandler接口
public class OwnerInvocationHandler implements InvocationHandler{
    PersonBean person;
    
    //我们将person传入构造器,并保持它的引用
    public OwnerInvocationHandler(PersonBean person){
        this.person = person;
    }
    
    //每次proxy的方法被调用,就会导致proxy调用此方法
    public Object invoke(Object proxy,Method method,Object[] args) throws IllegalAccessException{
        try{
            if(method.getName().startsWith("get")){
                //如果方法是一个getter,我们就调用person内的方法
                return method.invoke(person,args);
            }else if(method.getName.equals("setHotOrNotRating")){
                //如果是setHotOrNotRating()方法,我们就抛出IllegalAccessException表示不允许
                throw new IllegalAccessException();
            }else if(method.getName().startsWith("set")){
                //因为我们是拥有者,所以任何其他set方法都可以,我们就在真正主题上调用它
                return method.invoke(person,args);
            }
        }catch(InvocationTargetException e){
            //真正主题抛出异常的话,就会执行这里
            e.printStackTrace();
        }
        return null;
    }
}

步骤二:创建动态代理

创建动态Proxy类,并实例化Proxy对象。我们编写一个以PersonBean为参数,并知道如何为PersonBean对象创建拥有者代理的方法。也就是说,我们要创建一个代理,将它的方法调用转发给OwnerInvocationHandler。代码如下:

//此方法需要一个person对象作为参数,然后返回它的代理,因为代理和主题有相同的接口,所以我们返回一个PersonBean
PersonBean getOwnerProxy(PersonBean person){
    //利用Proxy类的静态newProxyInstance方法创建代理
    return (PersonBean)Proxy.newProxyInstance(person.getClass().getClassLoader(),
    //代理需要实现的接口
    person.getClass.getInterfaces(),
    new OwnerInvocationHandler(person));
}

步骤三:利用适当的代理包装任何PersonBean对象

来看下代理如何控制对setter方法的访问

public class MatchMakingTestDrive{
    public MatchMakingTestDrive(){
        initializeDatabase();
    }
    
    public void drive(){
        //从数据库中取出一个人
        PersonBean joe = getPersonFromDatabase("Joe");
        
        PersonBean ownerProxy = getOwnerProxy(joe);
        ownerProxy.setInterests("bowling,Go");
        try{
            ownerProxy.setHotOrNotRating(10);
        }catch(Exception e){
            System.out.println("Can't set rating from owner proxy");
        }
        
        
        PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
        //非拥有者代理
        nonOwnerProxy.setHotOrNotRating(10);
        try{
            nonOwnerProxy.setInterests("bowling,Go");
        }catch(Exception e){
            System.out.println("Can't set interests from non owner proxy");
        }
        
    }
}

总结

  1. “动态代理”动态在哪里?是不是指在运行时才将它实例化并和handler联系起来?

不是的。动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从你传入的接口集创建的。

  1. InvocationHandler看起来像一个很奇怪的proxy。它没有实现所代理的类的任何方法。

这是因为InvocationHandler根本就不是proxy,它只是一个帮助proxy的类,proxy会把调用转发给它处理。Proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态地创建的。

  1. 有没有办法知道某个类是不是代理类呢?

可以。代理类有一个静态方法,叫做isProxyClass()。此方法的返回值如果为true,表示这是一个动态代理类。除此之外,代理类还会实现特定的某些接口。

  • 代理模式
    • 包装另一个对象,并控制对它的访问
  • 适配器模式
    • 包装另一个对象,并提供不同的接口
  • 装饰者模式
    • 包装另一个对象,并提供额外的行为
  • 外观模式
    • 包装许多对象以简化它们的接口

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357339.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据挖掘,计算机网络、操作系统刷题笔记49

数据挖掘&#xff0c;计算机网络、操作系统刷题笔记49 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;orac…

Spring Cloud Alibaba 微服务简介

微服务简介 1 什么是微服务 2014年&#xff0c;Martin Fowler&#xff08;马丁福勒 &#xff09; 提出了微服务的概念&#xff0c;定义了微服务是由以单一应用程序构成的小服务&#xff0c;自己拥有自己的进程与轻量化处理&#xff0c;服务依业务功能设计&#xff0c;以全自动…

将Nginx 核心知识点扒了个底朝天(四)

为什么 Nginx 不使用多线程&#xff1f; Apache: 创建多个进程或线程&#xff0c;而每个进程或线程都会为其分配 cpu 和内存&#xff08;线程要比进程小的多&#xff0c;所以 worker 支持比 perfork 高的并发&#xff09;&#xff0c;并发过大会榨干服务器资源。 Nginx: 采用…

程序员35岁中年危机不是坎,是一把程序员自己设计的自旋锁

有时候&#xff0c;我会思考35岁这个程序员的诅咒&#xff0c;确切来说是中国程序员的独有的诅咒。 优秀的程序员思维逻辑严谨&#xff0c;弄清楚需求的本质是每天重复的工作&#xff0c;也是对工作的态度&#xff0c;那弄清楚诅咒的来源&#xff0c;义不容辞。 被诅咒的35岁 …

【爬虫】自动获取showdoc指定项目中的所有文档

▒ 目录 ▒&#x1f6eb; 导读需求1️⃣ 格式分析官方下载文件内容prefix_info.json文件格式2️⃣ 封包分析/api/page/info/api/item/info3️⃣ 编码代码特点问题&#x1f4d6; 参考资料&#x1f6eb; 导读 需求 showdoc是一个API文档、技术文档工具网站&#xff0c;经常能搜到…

String intern方法理解

1、原理 参考学习视频&#xff1a; https://www.bilibili.com/video/BV1WK4y1M77t/?spm_id_from333.337.search-card.all.click&vd_source4dc3f886f5ce1d43363b603935f02bd1 String s1 “hello”; String s1 "hello"; 代码原理解释如下图String s1 new Str…

进程章节总结性实验

进程实验课笔记 本节需要有linux基础&#xff0c;懂基本的linux命令操作即可。 Ubuntu镜像下载 https://note.youdao.com/s/VxvU3eVC ubuntu安装 https://www.bilibili.com/video/BV1j44y1S7c2/?spm_id_from333.999.0.0 实验环境ubuntu22版本&#xff0c;那个linux环境都可以…

Linux-VMware常用设置(时间+网络)及网络连接激活失败解决方法-基础篇②

目录一、设置时间二、网络设置1. 激活网卡方法一&#xff1a;直接启动网卡&#xff08;仅限当此&#xff09;方法二&#xff1a;修改配置文件&#xff08;永久&#xff09;2. 将NAT模式改为桥接模式什么是是NAT模式&#xff1f;如何改为桥接模式&#xff1f;三、虚拟机网络连接…

20230219 质心和重心的区别和性质

质心&#xff1a;&#xff08;无需重力场的前提&#xff09;所有质点的位置关于它们的质量的加权平均数。 重心&#xff1a;&#xff08;需要重力场的前提&#xff09;重力对系统中每个质点关于重心的力矩之和为零。 质心&#xff1a; xˉ∑i1nmixi∑i1nmi,yˉ∑i1nmiyi∑i1nmi…

Fiddler的报文分析

目录 1.Statistics请求性能数据 2.检测器&#xff08;Inspectors&#xff09; 3.自定义响应&#xff08;AutoResponder&#xff09; 1.Statistics请求性能数据 报文分析&#xff1a; Request Count: 1 请求数&#xff0c;该session总共发的请求数 Bytes …

vue3.0 生命周期

目录前言&#xff1a;vue3.0生命周期图例1.beforeCreate2.created3.beforeMount/onBeforeMount4.mounted/onMounted5.beforeUpdate/onBeforeUpdate6.updated/onUpdated7.beforeUnmount/onBeforeUnmount8.unmounted/onUnmounted案例&#xff1a;总结前言&#xff1a; 每个Vue组…

智慧城市应急指挥中心数字化及城市驾驶舱建设方案

目 录 第一章 项目概述 1.1 项目背景 1.2 项目范围 第二章 建设内容 2.1 三维可视化平台 2.1.1 多源数据接入 2.1.2 可视化编排 2.1.3 三维可视化编辑 2.1.4 空间数据可视化 2.1.5 集成框架支持 2.2 可视化场景定制开发 2.2.1 城市驾驶总舱 2.2.2 城市安全分舱 2.…

PLT/PDF转CAD:scViewerX 8.1 Crack

scViewerX是一个功能强大的 ActiveX 控件&#xff0c;允许您查看、打印和转换 PLT、Adobe PDF、Autodesk DWF、CGM、Calcomp、HPGL/2、Gerber、TIF、CALS 和其他几种格式。 ScViewerX 可以将您的文件转换为多种不同的输出文件格式&#xff0c;包括 PDF、PDF/A、TIFF、DXF、DWF、…

【人工智能AI】三、NoSQL 实战《NoSQL 企业级基础入门与进阶实战》

帮我写一篇介绍NoSQL的技术文章&#xff0c;文章标题是《NoSQL 实战》&#xff0c;不少于3000字。这篇文章的目录是 3.NoSQL 实战 3.1 MongoDB 入门 3.1.1 MongoDB 基本概念 3.1.2 MongoDB 安装与配置 3.1.3 MongoDB 数据库操作 3.2 Redis 入门 3.2.1 Redis 基本概念 3.2.2 Red…

windows微软商店下载应用失败/下载故障的解决办法;如何在网页上下载微软商店的应用

一、问题背景 设置惠普打印机时&#xff0c;需要安装hp smart&#xff0c;但是官方只提供微软商店这一下载渠道。 点击安装HP Smart&#xff0c;确定进入微软商店下载。 完全加载不出来&#xff0c;可能是因为开了代理。 把代理关了&#xff0c;就能正常打开了。 但是点击“…

IsADirectoryError: [Errno 21] Is a directory【已解决】

问题描述 生成数据&#xff0c;存储时候报错。 IsADirectoryError: [Errno 21] Is a directory: /home/LIST_2080Ti/njh/CHB-MIT-DATA/epilepsy_eeg_classification/data_processing/chb28/520.csv 问题分析 按我的认知&#xff0c;python执行的时候&#xff0c;比如这句 d…

FLAT:Flat-LAttice Transformer

中文NLP的一个问题&#xff0c;就是中文的字除了句句之间有标点符号之外都是连在一起的&#xff0c;不像英文词语是单独分割的。中文NLP处理一般会有2种方式&#xff1a;基于字的&#xff0c;char-level。现在比较常用的方法&#xff0c;但会缺少词组的语义信息。基于词的&…

TCP流套接字编程

ServerSocket API ServerSocket 是创建TCP服务端Socket的API。 ServerSocket 构造方法&#xff1a; ServerSocket 方法&#xff1a; Socket API Socket 是客户端Socket&#xff0c;或服务端中接收到客户端建立连接&#xff08;accept方法&#xff09;的请求后&#xff0…

【Java集合类】ArrayList

内部结构 ArrayList内部核心是一个Object数组elementDataObject数组的长度&#xff08;length&#xff09;视为ArrayList当前的容量&#xff08;capacity&#xff09;size对象表示ArrayList当前的元素个数 类上的重要注释 内部是Object数组 允许put null值,会自动扩容 size、…

基于springboot+vue的个人健康信息服务平台

基于springbootvue的个人健康信息服务平台 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背…