设计模式之代理模式

结构型模式之代理模式(静态代理)

  • 由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。
  • 代理其实是实现简介访问的媒介,当然在代理类中还可以在执行代理操作之前,之后,之中,环绕等执行相关动作。Spring 中面向切面编程就是这个原理
  • 代理模式是一种应用很广泛的结构型设计模式,而且变化形式非常多,常见的代理形式包括远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理等,后面将学习这些不同的代理形式
  • 当使用代理类的时候, 真实类中的信息对用户来说是透明的(不可见的)
  • 主要就是用于对象的间接访问提供了一个方案,可以对对象的访问进行控制

定义

代理模式

  1. Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
  2. Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
  3. RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

实例

第一个例子

  • 需求: 我们知道mac笔记本是在美国生产的,那么如果中国供销商想要卖mac笔记本,那么必须从美国供销商那里先进货,然后中国的顾客才可以在中国供销商买mac。这里的中国供销商就相当于代理,美国供销商就相当于真实主题角色
  • Mac笔记本抽象接口(相当于其中的抽象主题)
/*
 * 苹果笔记本的接口,其中有一个方法实现了买笔记本的动作
 */
public interface MacBook {
	public void buy();   //购买笔记本的行为
}
  • 美国供销商(相当于这里RealSubject)
/*
 * 美国的笔记本,实现了MacBook接口,表示在美国买笔记本
 */
public class USAMac implements MacBook {

	@Override
	public void buy() {
		System.out.println("在美国买笔记本");

	}
}
  • 中国供销商(相当于这里的代理角色)
    • 我们可以看到我们在使用代理模式的时候可以在之前和之后进行操作
/*
 * 中国的笔记本,实现了MacBook  表示在中国买笔记本
 * 但是中国想要买到苹果笔记本,那么还是需要先从美国进货,因此中国只是一个中间的代理作用而已
 * 当然代理的最大作用就是在代理之前、之后、之中执行相关的操作,这就是面向切面编程的原理
 */
public class ChinaMac implements MacBook {

	private MacBook mcBook=new USAMac();   //创建USAMac的对象

	/**
	 * 在购买之前执行的操作
	 */
	public void preBuy(){
		System.out.println("购买之前执行的操作");
	}

	/**
	 * 在购买之后执行的操作
	 */
	public void afterBuy(){
		System.out.println("购买之后执行的操作");
	}

	@Override
	public void buy() {
		this.preBuy();   //之前执行的操作
		mcBook.buy();  //在美国买笔记本
		System.out.println("在中国买笔记本");
		this.afterBuy();   //之后执行的操作
	}
}
  • 测试类
    • 我们在使用的时候直接使用代理类即可,我们根本不知道在真实类的使用,完全是代理类为我们提供了
public class Client {
	public static void main(String[] args) {
		MacBook macBook=new ChinaMac();   //创建ChinaMac对象,在中国买笔记本
		macBook.buy();    //直接在中国买笔记本

	}
}

第二个例子

  • 我们登录一个网站的服务器端的验证步骤:
    • 读取用户名和密码
    • 验证用户名和密码
    • 记录到日志中
  • 这里的验证密码和记录到日志中可以在代理类中实现,在用户执行操作之前需要读取用户名和密码,并且验证,在操作之后需要将用户的一些操作记录到日志中。其实这里的真实用户需要做的只是执行自己的操作,而验证和记录都是交给代理类实现的。

实现

  • 用户接口(User)
/*
 * 用户的抽象类
 */
public interface User {
	public void DoAction();   //执行动作

}
  • 真实的用户类(实现了用户接口)
    • 主要的做的就是执行自己的操作
public class RealUser implements User {
	public String name;
	public String password;
	public RealUser(String name, String password) {
		this.name = name;
		this.password = password;
	}
	public RealUser() {}
	/*
	 * 执行一些操作
	 */
	@Override
	public void DoAction() {
		System.out.println("开始执行操作......");
	}
}
  • 代理类(实现了User接口)
    • 在执行操作之前验证密码和用户名是否正确
    • 在执行操作之后记录到日志中
    • 实际上这里就是面向切面编程
public class ProxUser implements User {
	private RealUser user; // 真实用户的对象

	/**
	 * 创建对象
	 * @param name  姓名
	 * @param password   密码
	 */
	public ProxUser(String name, String password) {
		user = new RealUser(name, password);
	}

	@Override
	public void DoAction() {
		//验证用户名和密码
		if (Validate()) {
			user.DoAction();   //调用真实用户的DoAction方法执行相关操作
			logger();   //调用日志记录信息
		} else {
			System.out.println("请重新登录.......");
		}
	}

	/*
	 * 验证用户的用户名和密码
	 */
	public Boolean Validate() {
		if ("陈加兵".equals(user.name) && "123456".equals(user.password)) {
			return true;
		}
		return false;
	}

	/**
	 * 添加日志记录信息
	 */
	public void logger() {
		System.out.println(user.name + "登录成功......");
	}
}
  • 测试类
    • 实际上执行了验证用户名和密码,记录日志的操作,但是对于客户端来说只能看到自己执行的操作
public class Client {
	public static void main(String[] args) {
		ProxUser proxUser=new ProxUser("陈加兵", "123456");   //创建代理对象
		proxUser.DoAction();   //执行操作,实际执行了验证信息,doaction(),日志记录这个三个动作
	}
}

缺点

  • 如果增加一个接口就需要增加一个代理类,如果是要增加很多,那么就要增加很多代理类,代码将会重复

解决方法

  • 下面我们将会讲解到动态代理,仅仅需要一个代理类即可

结构型模式之动态代理模式

  • 前面我们说的代理模式其实是属于静态代理模式,就是说在程序执行之前已经写好了代理类,但是缺点也是说过,必须为每个接口都实现一个代理类,如果有多个接口需要代理,那么代码肯定是要重复的,因此就需要动态代理了。
  • 动态代理可以实现多个接口共用一个代理类,只需要改变初始化的参数即可,可以省去很多的重复的代码。
  • JDK的动态代理需要一个类一个接口,分别为Proxy和InvocationHandler
  • 主要原理就是利用了反射的原理

InvocationHandler

  • 这个是代理类必须实现的接口,其中有一个方法public Object invoke(Object proxy,Method method,Object[] args)
    • Object proxy:指被代理的对象。
    • Method method:要调用的方法
    • Object[] args:方法调用时所需要的参数

Proxy

  • Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法: public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    • ClassLoader loader:类加载器
    • Class<?>[] interfaces:得到全部的接口
    • InvocationHandler h:得到InvocationHandler接口的子类实例

实例

  • 肯德基的接口
/*
 * 肯德基的接口,其中一个eat方法
 */
public interface IKFC {
	public void eat();
}
  • 肯德基的实现类(RealSubject)
/*
 * IKFC的实现类
 */
public class KFC implements IKFC {

	@Override
	public void eat() {
		System.out.println("我在肯德基吃了饭......");

	}
}
  • 苹果笔记本的接口
/*
 * 苹果笔记本的接口
 */
public interface MacBook {
	public void buy();
}
  • 美国供销商的类(RealSubject)
/*
 * 美国笔记本的类,实现了MacBook接口
 */
public class USAMacBook implements MacBook {
	@Override
	public void buy() {
		System.out.println("在美国买了一个苹果电脑......");
	}

}
  • 动态代理的类(实现了InvocationHandler接口)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 这个是代理类,实现了InvocationHandler接口
 *
 */
public class ProxyHandler implements InvocationHandler {
	private Object Realobject;   //被代理的对象

	//构造方法,用来初始化被代理的对象
	public ProxyHandler(Object obj){
		this.Realobject=obj;   //初始化真实类的对象
	}

	/**
	 * @param proxy  表示被代理的对象的,就是真实类的对象
	 * @param method 表示要调用真实类的方法
	 * @param args  表示方法调用的时候所需要的参数
	 * @return   方法调用之后的返回值
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		prefunction();   //执行之前调用的方法
		Object res=method.invoke(Realobject, args);    //Method类中的执行方法的函数,在反射中常用
		afterFunction();   //执行之后调用的方法
		return res;
	}


	/**
	 * 执行方法之前调用的方法
	 */
	public void prefunction(){
		System.out.println("执行方法之前......");
	}

	/**
	 * 执行方法之后调用的方法
	 */
	public void afterFunction(){
		System.out.println("执行方法之后......");
	}
}
  • 测试类
import java.lang.reflect.Proxy;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.bcel.internal.util.Class2HTML;
public class Client {
	public static void main(String[] args) {

		Class[] cls1={IKFC.class};   //第一个代理的所有接口数组,直接用接口的反射即可

		Class[] cls2=USAMacBook.class.getInterfaces(); //直接具体的实现类的反射调用getInterfaces即可返回所有的接口数组

		// 返回KFC的代理对象
		IKFC kfc = (IKFC) Proxy.newProxyInstance(Client.class.getClassLoader(),
				cls1, new ProxyHandler(new KFC()));
		kfc.eat();   //执行方法

		MacBook macBook = (MacBook) Proxy.newProxyInstance(Client.class.getClassLoader(),
				cls2, new ProxyHandler(
						new USAMacBook()));

		macBook.buy();   //执行方法

	}
}

总结

  • 动态代理的好处
    • 即使有多个接口,也仅仅只有一个动态代理类

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Rgc

Python预编译语句防止SQL注入

今天也在找python的预编译,早上写的sql是拼接来构成的。于是找了2篇文章,还不错,分享一下大家学习。

1692
来自专栏微信公众号:Java团长

《深入理解Java虚拟机》笔记

也就是说,我们完全可以做一个工具,从一个文件中读入指令,然后将这些指令运行起来。上面代码中“编好的机器指令”当然指的是能在CPU上运行的,如果这里我还实现了一个...

601
来自专栏微信公众号:Java团长

什么是线程安全,你真的了解吗?

我们整天说线程安全,但是你对什么是线程安全真的了解嘛?说真的,我之前真的是了解甚微,那么我们今天就来聊聊这个问题。

1553
来自专栏杂七杂八

列表、字典、集合中筛选数据

传统迭代法 data = [1,5,-3,-2,8,0,9] res = [] for x in data: if x >=0: res...

3776
来自专栏向治洪

命令模式

命令模式定义 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 作用 命令模式主要应用于将行...

1989
来自专栏西安-晁州

easyui表单校验拓展

/** * Created by chaozhou on 2016/5/30. */ /** * 扩展的基本校验规则, */ $.extend($.fn...

2190
来自专栏linux、Python学习

十分钟带你了解 Python3 多线程核心知识

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

2090
来自专栏hbbliyong

vue.js如何在标签属性中插入变量参数

html的标签的属性,比如id、class、href需要动态传递参数,拼接字符串,查了一些资料,并没有找到合适的解决方法,琢磨了一上午,终于试出了方法: ? v...

42511
来自专栏章鱼的慢慢技术路

记账类问题汇总

1865
来自专栏noteless

javaweb请求编码 url编码 响应编码 乱码问题 post编码 get请求编码 中文乱码问题 GET POST参数乱码问题 url乱码问题 get post请求乱码 字符编码

然后使用      65------>$ 另外一种解码方式解读,显然A就变成了$,这不就是乱码了么

1773

扫码关注云+社区

领取腾讯云代金券