• 从王者荣耀看设计模式(保护代理模式)


    从王者荣耀看设计模式(保护模式)

    一.简介

    打开王者荣耀,点击右上角头像标志可进入我的信息主页。在我的个人主页中,我可以可设置玩家的游戏昵称,性别,常用英雄。除此之外,我还可以查看自己得到别人点赞的次数(不能给自己点赞);其他玩家亦可访问我的主页,可以给我点赞,可以查看我设置的所有信息(不能更改)。

    模式动机
    在本实例中,使用保护代理来限制用户的使用权限。我可以修改做自己的主页信息却不能给自己点赞。其他玩家可以给我点赞却不能修改我的主页信息。

    二.保护代理(控制用户的访问权限)

    保护代理,是提供某些级别的保护,根据客户的权限和决定客户可否访问哪些特定的方法,所以保护代理可能只提供给客户部分接口。

    三.Java动态代理

    Java在java.lang.reflect包中有自己的代理支持,利用这个包可以在运行时动态地创建一个代理类,实现一个或多个接口。并将方法的调用转发到你所指定的类。因为实际的代理类是在运行时创建的,我们称这个Java技术为:动态代理
    (

    动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
    java的反射机制
    我们需要利用Java的反射机制实现动态代理来创建保护代理。但在这之前,让我们先看一下类图,了解一下动态代理。

    因为Java已经为我们创建了Proxy类,所以只需要有方法来告诉Proxy类要做什么。不能像以前一样把代码放进Proxy类中,以为Proxy不是我们直接实现的。既然代码不能放到Proxy类中,那么需要放到哪里?放在InvocationHandler中,InvocationHandler的工作是响应代理的任何调用。可以把InvocationHandler想成是代理收到方法调用后,请求做实际工作的对象。每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法

    Object invoke(Object proxy,Method method,Object[] args) throws Throwable
    @param proxy:指代我们所代理的那个真实对象
    @param method:指代的是我们所要调用真实对象的某个方法的Method对象
    @param args:指代的是调用真实对象某个方法时接收的参数。

    (_−)☆o(´`)oo(╥﹏╥)oヽ(ー_ー)ノ(•́へ•́╬)ψ(*`ー´)ψ!!!∑(゚Д゚ノ)ノ|ू・ω・` )

    在让我们看看Proxy这个类:

    Proxy provides static methods for creating dynamic proxy classes and instances, and is also the superclass of all dynamic proxy classes created by those methods.
    Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多方法,但是我们用的最多的就是newProxyInstance这个方法:
    public static Object newProxyInstance(ClassLoader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
    Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
    这个方法的作用就是得到一个动态的代理对象,其接收三个参数,让我们来看看这三个参数所代表的意义:
    public static Object newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException
    @param loader:一个ClassLoader对象,定义了那个ClassLoader对象来对生成的代理对象进行加载
    @param interface:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么借口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用遮住接口中的方法了。**我们给代理对象提供了一组什么借口,那么我这个代理对象就会实现这组接口)
    @param h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

    四.结构图

    五.设计类图

    六.代码实现

    步骤一:编写通用方法接口以及接口实现
    保护代理模式目标是实现对访问类的控制
    PersonBean类(主题接口)

    package com.practice.Player;
    /*
     * 这个接口可以设置和取得人的名字、性别、常用英雄和赞数量
     */
    public interface PersonBean {
    	String getPlayerID();
    	String getGender();
    	String getCommonHero();
    	int getYesNumber();
    	
    	void setPlayerID(String PlayerID);
    	void setGender(String gender);
    	void setCommonHero(String CommonHero);
    	void setYesNumber();
    	void setAllCount(int Count);
    }
    

    PersonBeanImpl类(主题类)

    package com.practice.Person.Impl;
    
    import com.practice.Player.PersonBean;
    
    public class PersonBeanImpl implements PersonBean {
    	String playerID;
    	String gender;
    	String CommonHero;
    	int yesAllCount;
    
    	public String getPlayerID() {
    		return playerID;
    	}
    
    	public String getGender() {
    		return gender;
    	}
    
    	public String getCommonHero() {
    		return CommonHero;
    	}
    
    	public int getYesNumber() {
    		return yesAllCount;
    	}
    
    	public void setPlayerID(String playerID) {
    		this.playerID = playerID;
    	}
    
    	public void setGender(String gender) {
    		this.gender = gender;
    	}
    
    	public void setCommonHero(String CommonHero) {
    		this.CommonHero = CommonHero;
    	}
    
    	public void setYesNumber() {
    		yesAllCount++;
    	}
    
    	public void setAllCount(int Count) {
    		this.yesAllCount = Count;
    	}
    }
    

    步骤二:创建两个InvocationHandler
    InvocationHandler实现了代理的行为,Java负责创建真实代理类和对象。我们只需提供在方法调用发生时知道做什么的handler。我们需要写两个InvocationHandler(调用处理器),其中OwnerHandler类给调用者使用.NonOwnerHandler给非拥有者使用。InvocationHandler::当代理方法被调用时,代理就会把这个调用转发给InvocaionHandler,但是这并不是通过调用InvocationHandler的对应方法做到的。而是给拥有者使用的OwnerHandler类

    OwnerInvacationHandler类(给拥有者使用)

    package com.practice.Proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import com.practice.Player.PersonBean;
    
    public class OwnerInvocationHandler implements InvocationHandler {
    	PersonBean person;
    
    	public OwnerInvocationHandler(PersonBean person) {
    		this.person = person;
    	}
    	
    	public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException{
    		try {
    			if(method.getName().startsWith("get")) {
    				return method.invoke(person, args);
    			}else if(method.getName().equals("setYesNumber")) {
    				throw new IllegalAccessException();
    			}else if(method.getName().startsWith("set")) {
    				return method.invoke(person, args);
    			}
    		}catch(InvocationTargetException e) {
    			e.printStackTrace();
    		}
    		return null;
    	}
    
    }
    

    NonOwnerInvocationHandler类(给非拥有者使用)

    package com.practice.Proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import com.practice.Player.PersonBean;
    
    public class NonOwnerInvocationHandler implements InvocationHandler {
    	PersonBean person;
    	
    	public NonOwnerInvocationHandler(PersonBean person) {
    		this.person = person;
    	}
    
    	public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException{
    		try {
    			if(method.getName().startsWith("get")) {
    				return method.invoke(person, args);
    			}else if(method.getName().equals("setYesNumber")){
    				return method.invoke(person, args);
    			}else if(method.getName().startsWith("set")) {
    				throw new IllegalAccessException();
    			}
    		}catch(InvocationTargetException e) {
    			e.printStackTrace();
    		}
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    }
    

    步骤三. 创建Proxy类并实例化Proxy对象
    开始编写一个以PersonBean为参数,并知道如何为PersonBean对象创建拥有者代理的方法,也就是说,我们要创建一个代理,将它的方法调用转发给OwnerInvocationHandler

    获取拥有者方法代理

    	PersonBean getOwnerProxy(PersonBean person) {
            // 我们利用Proxy类的静态newProxyInstance方法创建代理对象(Java反射机制)
            return (PersonBean) Proxy.newProxyInstance( 
                	person.getClass().getClassLoader(),  // 将personBean的类载入器当作参数
                	person.getClass().getInterfaces(),   // 代理需要实现的接口
                    new OwnerInvocationHandler(person)); // 调用非拥有者的处理器
    	}
    

    获取非拥有者方法代理

    	PersonBean getNonOwnerProxy(PersonBean person) {
    		
            return (PersonBean) Proxy.newProxyInstance(
                	person.getClass().getClassLoader(),   
                	person.getClass().getInterfaces(),   
                    new NonOwnerInvocationHandler(person));
    	}
    

    步骤四.测试类
    测试配对服务系统。利用适当的代理包装任何PersonBean对象
    MatchMakingTestDrive类

    package com.practice.Test;
    import java.lang.reflect.*;
    import java.util.*;
    
    import com.practice.Person.Impl.PersonBeanImpl;
    import com.practice.Player.PersonBean;
    import com.practice.Proxy.NonOwnerInvocationHandler;
    import com.practice.Proxy.OwnerInvocationHandler;
     
    public class MatchMakingTestDrive {
        // 实例变量, 当作是保存顾客的“数据库”
    	Hashtable<String, PersonBean> datingDB = new Hashtable<String, PersonBean>();
     	
    	public static void main(String[] args) {
    		MatchMakingTestDrive test = new MatchMakingTestDrive();
    		test.drive();
    	}
     
    	public MatchMakingTestDrive() {
            // 在构造器中初始化数据库
    		initializeDatabase();
    	}
     
    	public void drive() {
    		PersonBean MW = getPersonFromDatabase("妙乌");  //从数据库中取出一个人
    		PersonBean ownerProxy = getOwnerProxy(MW); // 创建这个人的拥有者代理
    		System.out.println("账号拥有者的游戏ID " + ownerProxy.getPlayerID()); //  输出玩家昵称
    		ownerProxy.setCommonHero("王昭君");  // 使用拥有者代理来设置自己常用英雄
    		System.out.println("账号拥有者设置游戏常用英雄");
    		System.out.println("账号拥有者常用的英雄:"+ownerProxy.getCommonHero());
    		try {
                // 尝试用拥有者代理来给自己点赞
    			ownerProxy.setYesNumber();
    		} catch (Exception e) {
                // 如果给自己点赞会出错
    			System.out.println("账号拥有者不能够给自己点赞");
    		}
    		System.out.println("账号总赞数为: " + ownerProxy.getYesNumber());
    		
    		System.out.println();
     
            // 创建一个非拥有者的代理
    		PersonBean nonOwnerProxy = getNonOwnerProxy(MW);
    		System.out.println("账号拥有者的游戏ID " + nonOwnerProxy.getPlayerID());
    		try {
                // 尝试用非拥有者代理来设置常用英雄
    			nonOwnerProxy.setCommonHero("妲己");
    		} catch (Exception e) {
                // 不可以给别人设置常用英雄
    			System.out.println("非拥有者不能设置别人的常用英雄");
    		}
            // 可以给别人点赞
    		nonOwnerProxy.setYesNumber();
    		// 查看信息
    		System.out.println("非拥有者玩家查看账号所有者信息:");
    		System.out.println("昵称:" + nonOwnerProxy.getPlayerID() + "	性别:" + nonOwnerProxy.getGender()
    				+ "	常用英雄:" + nonOwnerProxy.getCommonHero());
    		System.out.println("非拥有者玩家点赞");
    		System.out.println("总赞数为: " + nonOwnerProxy.getYesNumber());
    	}
     
        // 此方法需要一个person对象作为参数,然后返回该对象的代理
        // 因为代理和主题有相同的接口,所以我们返回接口PersonBean
    	PersonBean getOwnerProxy(PersonBean person) {
            // 我们利用Proxy类的静态newProxyInstance方法创建代理对象(Java反射机制)
            return (PersonBean) Proxy.newProxyInstance( 
                	person.getClass().getClassLoader(),  // 将personBean的类载入器当作参数
                	person.getClass().getInterfaces(),   // 代理需要实现的接口
                    new OwnerInvocationHandler(person)); // 调用非拥有者的处理器
    	}
     
    	PersonBean getNonOwnerProxy(PersonBean person) {
    		
            return (PersonBean) Proxy.newProxyInstance(
                	person.getClass().getClassLoader(),   
                	person.getClass().getInterfaces(),   
                    new NonOwnerInvocationHandler(person));
    	}
     
    	PersonBean getPersonFromDatabase(String name) {
    		return (PersonBean)datingDB.get(name);
    	}
     
        // 初始化“数据库”
    	void initializeDatabase() {
    		PersonBean MW = new PersonBeanImpl();
    		MW.setPlayerID("妙乌");
    		MW.setGender("男");
    		MW.setCommonHero("狄仁杰");
    		MW.setAllCount(7);
    		datingDB.put(MW.getPlayerID(), MW);
     
    		PersonBean ZX = new PersonBeanImpl();
    		ZX.setPlayerID("Kelly Klosure");
    		ZX.setCommonHero("ebay, movies, music");
    		ZX.setYesNumber();
    		datingDB.put(ZX.getPlayerID(), ZX);
    	}
    }
    

    运行结果:

    七.源代码下载

    从王者荣耀看设计模式(保护代理)

  • 相关阅读:
    28. css样式中px转rem
    27.用webpack自搭react和vue框架
    26.webpack 入门
    25.redux回顾,redux中的action函数异步
    24.redux
    23.react-router 路由
    22.2、react生命周期与react脚手架(二)
    22.1 、react生命周期(一)
    21.react 组件通信
    const关键字的作用
  • 原文地址:https://www.cnblogs.com/miaowulj/p/12111133.html
Copyright © 2020-2023  润新知