大话设计模式七:代理模式(proxy)

一. 定义

为其他对象提供一种代理以控制对这个对象的访问。


二. 结构和说明


Subject:目标接口,定义代理和具体目标对象的接口

public interface Subject {
	// 示意方法:一个抽象的请求方法
	public void request();
}
RealSubject:具体的目标对象,真正实现目标接口要求的功能
public class RealSubject implements Subject{
	public void request() {
		//执行具体的功能处理
	}
}
Pxory:代理对象,通常具有如下功能:

1. 实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象

2.保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象

3.可以控制对具体目标对象的访问

public class Proxy implements Subject {
	// 持有被代理的具体的目标对象
	private RealSubject realSubject = null;

	/**
	 * 构造方法,传入被代理的具体的目标对象
	 * @param realSubject 被代理的具体的目标对象
	 */
	public Proxy(RealSubject realSubject) {
		this.realSubject = realSubject;
	}

	public void request() {
		// 在转调具体的目标对象前,可以执行一些功能处理
		// 转调具体的目标对象的方法
		realSubject.request();
		// 在转调具体的目标对象后,可以执行一些功能处理
	}
}


三. 示例:静态代理

现在有一个订单系统,要求是:一旦订单被创建,只有订单的创建人才能修改订单中的数据,其他人不能修改。

相当于现在如果有了一个订单对象实例,那么就需要控制外部对它的访问,满足条件的可以访问,而不满足条件的就不能访问了。

1. 订单对象的接口:

public interface OrderApi {
	// 获取订单订购的产品名称
	public String getProductName();

	// 设置订单订购的产品名称
	public void setProductName(String productName, String user);

	// 获取订单订购的数量
	public int getOrderNum();

	// 设置订单订购的数量
	public void setOrderNum(int orderNum, String user);

	// 获取创建订单的人员
	public String getOrderUser();

	// 设置创建订单的人员
	public void setOrderUser(String orderUser, String user);
}
2. 订单对象
public class Order implements OrderApi {
	// 订单订购的产品名称
	private String productName;
	
	// 订单订购的数量
	private int orderNum;
	
	// 创建订单的人员
	private String orderUser;

	/**
	 * 构造方法,传入构建需要的数据
	 */
	public Order(String productName, int orderNum, String orderUser) {
		this.productName = productName;
		this.orderNum = orderNum;
		this.orderUser = orderUser;
	}

	// getter and setter
}
3. 订单的代理对象
public class OrderProxy implements OrderApi {
	// 持有被代理的具体的目标对象
	private Order order = null;

	/**
	 * 构造方法,传入被代理的具体的目标对象
	 * @param realSubject 被代理的具体的目标对象
	 */
	public OrderProxy(Order realSubject) {
		this.order = realSubject;
	}

	public void setProductName(String productName, String user) {
		// 控制访问权限,只有创建订单的人员才能够修改
		if (user != null && user.equals(this.getOrderUser())) {
			order.setProductName(productName, user);
		} else {
			System.out.println("对不起" + user + ",您无权修改订单中的产品名称。");
		}
	}

	public void setOrderNum(int orderNum, String user) {
		// 控制访问权限,只有创建订单的人员才能够修改
		if (user != null && user.equals(this.getOrderUser())) {
			order.setOrderNum(orderNum, user);
		} else {
			System.out.println("对不起" + user + ",您无权修改订单中的订购数量。");
		}
	}

	public void setOrderUser(String orderUser, String user) {
		// 控制访问权限,只有创建订单的人员才能够修改
		if (user != null && user.equals(this.getOrderUser())) {
			order.setOrderUser(orderUser, user);
		} else {
			System.out.println("对不起" + user + ",您无权修改订单中的订购人。");
		}
	}

	public int getOrderNum() {
		return this.order.getOrderNum();
	}

	public String getOrderUser() {
		return this.order.getOrderUser();
	}

	public String getProductName() {
		return this.order.getProductName();
	}

	public String toString() {
		return "productName: " + this.getProductName() + ", orderNum: " + this.getOrderNum() + ",orderUser: " + this.getOrderUser();
	}
}
4. 客户端
public class Client {
	public static void main(String[] args) {
		// 张三先登录系统创建了一个订单
		OrderApi order = new OrderProxy(new Order("设计模式", 100, "张三"));
		
		// 李四想要来修改,那就会报错
		order.setOrderNum(123, "李四");
		
		// 输出order
		System.out.println("李四修改后订单记录没有变化:" + order);
		
		// 张三修改就不会有问题
		order.setOrderNum(123, "张三");
		
		// 再次输出order
		System.out.println("张三修改后,订单记录:" + order);
	}
}
对不起李四,您无权修改订单中的订购数量。
李四修改后订单记录没有变化:productName: 设计模式, orderNum: 100,orderUser: 张三
张三修改后,订单记录:productName: 设计模式, orderNum: 123,orderUser: 张三


上面的实现就是java的静态代理,这种实现方式有一个较大的缺点,就是如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不是很灵活。

四. 示例:动态代理

public class DynamicProxy implements InvocationHandler {
	// 被代理的对象
	private OrderApi order = null;

	/**
	 * 获取绑定好代理和具体目标对象后的目标对象的接口
	 * @param order 具体的订单对象,相当于具体目标对象
	 * @return 绑定好代理和具体目标对象后的目标对象的接口
	 */
	public OrderApi getProxyInterface(Order order) {
		// 设置被代理的对象,好方便invoke里面的操作
		this.order = order;
		
		// 把真正的订单对象和动态代理关联起来
		OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
				order.getClass().getClassLoader(), 
				order.getClass().getInterfaces(), 
				this);
		return orderApi;
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 如果是调用setter方法就需要检查权限
		if (method.getName().startsWith("set")) {
			// 如果不是创建人,那就不能修改
			if (order.getOrderUser() != null && order.getOrderUser().equals(args[1])) {
				// 可以操作
				return method.invoke(order, args);
			} else {
				System.out.println("对不起," + args[1] + ",您无权修改本订单中的数据");
			}
		} else {
			// 不是调用的setter方法就继续运行
			return method.invoke(order, args);
		}
		return null;
	}
}
动态代理和静态代理相比,明显的变化是:虽然Subject接口定义了很多方法,但是动态代理类始终只有一个invoke方法(代理类是动态生成的),这样当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了

五. 何时选用代理模式

1. 如果需要控制对原始对象的访问的时候,如权限控制

2. 需要在访问对象的时候执行一些附加操作的时候,例如打印日志等


你可能感兴趣的:(设计模式,代理模式)