• 从王者荣耀看设计模式(远程代理模式)


    从王者荣耀看设计模式(远程代理模式)

    一.简介

    每位王者荣耀玩家都有一个属于自己的游戏账号。为了提升游戏等级或者增加游戏体验感,会存在多个游戏玩家同时共享一个游戏账号的情况。当一位玩家使用账号正在游戏中时,另一玩家登陆同一账号会导致前一玩家强制退出登陆!(ಥ_ಥ)

    模式动机
    在本实例中,通过远程代理,我们可以实现远程控制。当我处于在线状态时,使用代理让同一账号的使用者下线

    二.远程代理(帮助我们控制访问远程对象)

    远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果返回给客户。
    代理,就是代表某一真实对象,假装它是真正的对象。但是其实一切的动作是代理对象利用网络和真正的对象沟通o(´^`)o 代理之所以需要控制访问,是因为我们的客户不知道如何和远程对象沟通。从某些方面来看,远程代理控制访问,可以帮助我们处理网络上的细节
    变量只能引用和当前代码语句在同一堆空间中的对象,Java有内置远程调用的功能可以帮助我们实现远程代理

    三.结构图

    四.设计类图

    五.代码实现

    AccountStatus类(远程接口类)

    package com.practice.MyRemote;
    
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    /*
     * 客户端和服务端统一的接口,只需要服务端根据这个接口实现相应的功能,然后注册上去,
     * 客户端就可以根据这个接口来使用相应的功能
     *
     * 定义一个远程接口,必须继承Remote接口,其中需要远程调用的方法必须抛出RemoteException异常
     */
    public interface AccountStatus extends Remote {
    	public void OnlineStatus() throws RemoteException;//玩家在线状态
    	public void OfflineStatus() throws RemoteException;//玩家下线状态
    }
    

    AccountStatusHelper(接口实现类<代理>)

    package com.practice.MyRemote.Imple;
    
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    import com.practice.MyRemote.AccountStatus;
    
    public class AccountStatusHelper extends UnicastRemoteObject implements AccountStatus {
    	private static final long serialVersionUID = 1L;
    	@SuppressWarnings("unused")
    	private boolean Online = false;
    
    	public AccountStatusHelper(boolean _Online) throws RemoteException {
    		this.Online = _Online;
    	}
    	
    	@Override
    	public void OnlineStatus() throws RemoteException {
    		System.out.println("玩家1正使用账号游戏中……");
    	}
    
    	@Override
    	public void OfflineStatus() throws RemoteException {
    		System.out.println("玩家1退出王者荣耀账号!");
    	}
    
    }
    

    Server(服务类)

    package com.practice.MyRemote.Server;
    
    import java.net.MalformedURLException;
    import java.rmi.AlreadyBoundException;
    import java.rmi.Naming;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    
    import com.practice.MyRemote.AccountStatus;
    import com.practice.MyRemote.Imple.AccountStatusHelper;
    
    /**
     * 创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
     */
    
    public class Server {
    	private static final String HOST = "localhost";
    	private static final int PORT = 9090;
    	
    	public static void main(String [] args) {
    		try {
    			//创建对象, 准备将这个对象作为远程对象注册
    			AccountStatus accountStatusHelper = new AccountStatusHelper(true);
    			
    			LocateRegistry.createRegistry(PORT);
    			//绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
    			Naming.bind("rmi://" + HOST + ":" + PORT + "/AccountStatusHelper",accountStatusHelper);
    			System.out.println("---->远程对象绑定成功!");
    		}catch(RemoteException e) {
    			System.out.println("创建远程对象发生异常!");
    			e.printStackTrace();
    		}catch(AlreadyBoundException e) {
    			System.out.println("发生重复绑定对象异常!");
    			e.printStackTrace();
    		}catch(MalformedURLException e) {
    			System.out.println("发生URL畸形异常!");
    			e.printStackTrace();
    		}
    	}
    }
    

    Client类(客户类)

    package com.practice.Client;
    
    import java.net.MalformedURLException;
    import java.rmi.Naming;
    import java.rmi.NotBoundException;
    import java.rmi.RemoteException;
    
    import com.practice.MyRemote.AccountStatus;
    /*
     * 用于调用远程服务
     * 
     * 客户端测试,在客户端调用远程对象上的远程方法,并返回结果
     */
    
    public class Client {
    	public static int flag = 0;
    	public static void main(String[] args){
    		new Thread(new Runnable() {
    			public void run() {
    				try {
    					AccountStatus accountStatus;
    					accountStatus = (AccountStatus)Naming.lookup("rmi://127.0.0.1:9090/AccountStatusHelper");
    					accountStatus.OnlineStatus();
    					Thread.sleep(1000);
    					if(flag == 1) {
    						accountStatus.OfflineStatus();
    					}
    				} catch (NotBoundException | MalformedURLException | RemoteException | InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				} 
    			}
    		}).start();
    		System.out.println("玩家2通过QQ登陆王者荣耀");
    		flag = 1;
    	}
    }
    

    六.运行结果

    ⑴先运行Server类
    运行结果:

    ⑵运行Client类

    JAVA RMI(Remote Method Invoke)

    RMI提供了客户辅助对象(Stub)和服务器辅助对象(Skeleton),为客户辅助对象创建和服务对象相同的方法。RMI的好处在于你不必亲自写任何网络或I/O的代码。客户程序调用远程方法(即真正的服务所在)就和运行在客户自己的本地JVM上对对象进行正常方法调用一样。RMI提供了所有运行时的基础设施
    RMI将客户辅助对象称为stub(桩),服务辅助对象称为skeleton(骨架)

    RMI方法调用是如何发生的

    ①客户对象调用客户辅助对象的doSomeThing()方法。

    ②客户辅助对象打包调用信息(变量、方法名等),然后通过网络将它运给服务辅助对象

    ③服务辅助对象把来自客户辅助对象的信息解包,找出被调用的方法(以及在哪个对象内),然后调用真正的服务对象上的真正方法

    ④服务对象上的方法被调用,将结果返回给服务辅助对象

    ⑤服务辅助对象把调用的返回信息打包,然后通过网络运回给客户辅助对象

    ⑥服务辅助对象把返回值解包,返回给客户对象,对于客户对象来说,这是完全透明的

    制作远程服务

    第一步:制作远程接口

    远程接口定义出可以让客户远程调用的方法。客户将用它作为服务的类类型。Stub和实际的服务都实现此接口。
    ⑴ 扩展java.rmi.Remote。Remote是一个"记号"接口,所以Remote不具有方法,对于RMI来说,Remote接口具有特别的意义,所以我们必须遵守规则

    import java.rmi.Remote;
    import java.RemoteException;
    public interface MyRemote extends Remote{
        ……
    }
    

    ⑵声明所有的方法都会抛出RemoteException
    客户使用远程接口调用服务。换句话说,客户会调用实现远程接口的Stub上的方法,而Stub底层用到了网络和I/O,所以所有坏事情都有可能发生。客户必须认识到此风险,通过处理或声明远程异常来解决。如果接口的方法声明了异常,任何在接口类型的引用上调用方法的代码也必须处理或声明异常。

    import java.rmi.Remote;
    import java.rmi.RemoteException;
    
    public interface MyRemote extends Remote{
    	public String sayHello() throws RemoteException;
    }
    

    ⑶确定变量和返回值是否属于原语(primitive)类型或者可序列化(Serializable)类型
    远程方法的变量和返回值,必须属于源于类型或Serializable类型。远程方法的变量必须被打包并通过网络运送,这要靠序列化实现。如果使用原语类型、字符串和许多API中内定的类型(包括数组和集合),都不会有问题。如果传送的是定义的类,就必须保证类实现了Serializable接口

    第二步:制作远程实现

    这是做实际工作的类,为远程接口中的定义的远程方法提供真正的实现
    ⑴扩展UnicastRemoteObject
    为了要成为远程对象,对象需要某些"远程的"功能。最简单的方式就是扩展java.rmi.server.UnicastRemoteObject,让超类完成基本工作

    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    
    public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
        ……
    }
    

    ⑵实现客户想要调用的接口的方法

    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    
    public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    	private static final long serialVersionUID = 1L;
        /*
        /*UnicastRemoteObject的构造器会抛出RemoteException.当类被实例化时
        /*候,如果超类的构造器抛出异常,那么只能声明子类的构造器也抛出异常
        */
    	protected MyRemoteImpl() throws RemoteException {}
        
    	public String sayHello() throws RemoteException {
    		return "server says:'Hello'";
    	}	
    }
    

    ⑶用RMI Registry注册此服务
    目前,我们已经有了MyRemoteImpl这个远程服务,必须让它可以被远程客户调用。我们要做的就是将此服务实例化,然后放进RMI Registry中(要先确定RMI Registry正在运行,否则注册失败)。当注册这个对象时,RMI系统其实注册的是Stub,因为这是客户真正需要的。注册服务使用了java.rmi.Naming类的静态bind方法

    package com.practice.SayHello;
    import java.net.MalformedURLException;
    import java.rmi.AlreadyBoundException;
    import java.rmi.Naming;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
     
    /**
     * 创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
     */
    public class Server {
        private static final String HOST = "localhost";
        private static final int PORT = 9090;
     
    	public static void main(String args[]) {
     
            try {
                //创建2个对象, 准备将这个两个对象作为远程对象注册
    			MyRemote MyRemoteHelper = new MyRemoteImpl();
     
                LocateRegistry.createRegistry(PORT);
     
                //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
    
                Naming.bind("rmi://" + HOST + ":" + PORT + "/MyRemoteImpl", MyRemoteHelper);
     
                System.out.println("---->远程对象绑定成功!");
            } catch (RemoteException e) {
                System.out.println("创建远程对象发生异常!");
                e.printStackTrace();
            } catch (AlreadyBoundException e) {
                System.out.println("发生重复绑定对象异常!");
                e.printStackTrace();
            } catch (MalformedURLException e) {
                System.out.println("发生URL畸形异常!");
                e.printStackTrace();
            }
        }
    }
    

    ⑷运行Main程序
    main方法首先会实例化一个服务对象,然后到RMI registry中注册,注册后,这个服务就可以供客户调用了。

    第三步.客户端程序

    客户取得stub对象(我们的代理)以调用其中的方法。所以我们需要RMI Registry的帮
    忙。客户从Registry中寻找(lookup)代理,就像根据名字在电话簿里寻找一样

    ⑴客户到RMI Register中查找

    Naming.lookup("rmi://127.0.0.1/RemoteHelper");//RemoteHelper是在服务端绑定时用的名称

    ⑵RMI Register返回Stub对象
    作为lookup方法的返回值,RMI会自动对stub反序列化(在客户端必须有stub类)

    MyRemote accountStatus=(MyRemote)Naming.lookup("rmi:/127.0.0.1:9090/MyRemoteImpl");

    ⑶客户调用stub的方法,就像stub就是真正的服务对象一样
    完整的客户端代码:

    import java.net.MalformedURLException;
    import java.rmi.AlreadyBoundException;
    import java.rmi.Naming;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
     
    /**
     * 创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
     */
    public class Server {
        private static final String HOST = "localhost";
        private static final int PORT = 9090;
     
    	public static void main(String args[]) {
     
            try {
                //创建2个对象, 准备将这个两个对象作为远程对象注册
    			MyRemote MyRemoteHelper = new MyRemoteImpl();
     
                LocateRegistry.createRegistry(PORT);
     
                //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
    
                Naming.bind("rmi://" + HOST + ":" + PORT + "/MyRemoteImpl", MyRemoteHelper);
     
                System.out.println("---->远程对象绑定成功!");
            } catch (RemoteException e) {
                System.out.println("创建远程对象发生异常!");
                e.printStackTrace();
            } catch (AlreadyBoundException e) {
                System.out.println("发生重复绑定对象异常!");
                e.printStackTrace();
            } catch (MalformedURLException e) {
                System.out.println("发生URL畸形异常!");
                e.printStackTrace();
            }
        }
    }
    

    **⑷运行Main方法

    七.源代码下载

    从王者荣耀看设计模式(远程代理模式)

  • 相关阅读:
    最短路径算法
    二叉树遍历的应用
    二叉搜索树
    二叉树的遍历
    Linux 用户和用户组管理-用户信息文件
    Linux脚本安装包
    Linux 源码包安装过程
    RPM包管理-yum在线管理
    Linux RPM命令查询
    Linux RPM管理命令
  • 原文地址:https://www.cnblogs.com/miaowulj/p/12051411.html
Copyright © 2020-2023  润新知