老僧长谈设计模式-7-代理模式

声明:

本节内容主要来自Head First

【what】
代理模式是什么?

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

首先来一个简单的图,有一个初始的印象吧:




代理模式的种类主要分为:

远程(Remote)代理

虚拟(Virtual)代理


【How】

还记得上一章状态模式里的糖果机吗?

上一次的解决方案很不错,但还没有彻底的解决问题。

现在糖果公司ceo提出了新的需求:

ceo想要看到库存和糖果机状态的报告,想远程监控糖果机。

远程代理:


客户辅助对象(Client helper)并不真正拥有客户所期望的方法逻辑,它会联系服务器,传送方法调用信息,然后等待服务器返回。
在服务器端,服务辅助对象(Service helper)从客户辅助对象中接收请求(通过Socket连接),将调用的信息解包,然后调用真正服务对象上的方法。


对于服务对象(Service object)来说,调用是本地的,来自服务辅助对象,而不是远程客户。
服务辅助对象得到返回值,将它打包,然后运回到客户辅助对象(通过网络Socket的输出流),客户辅助对象将信息解包,最后将返回值交给客户对象。


RMI的调用过程(这部分讲的挺好):







------------------------------------------------------------------------------------------------------------------------

RMI 描述


制作远程服务的步骤:

①制作远程接口

1>扩展java.rmi.Remoto

2>声明所有的方法都会抛出RemotoException

3>确定变量和返回值是属于primitive类型或者可序列化Serializable



②制作远程实现

1>实现远程接口

你的服务必须实现远程接口,也就是客户将要调用的方法的接口。


2>扩展UnicastRemoteObject.


3>设计一个不带变量的构造器,并声明RemoteException


4>用RMI Registry注册此服务



③产生Stub和Skeleton

在远程实现类(不是远程接口)上执行rmic


④执行remiregistry


⑤启动服务



服务端的代码

远程接口

// 这个接口扩展自Remote,支持远程调用  
public interface MyRemote extends Remote{  
    // 每次远程调用都是“有风险的”,因为会设计网络和I/O  
    // 所以这个接口的方法都要声明异常  
    // 还要确定方法的返回值是primitive类型或者可序列化(Serializable)类型  
    // 远程方法的变量会被打包并通过网络运送,这要靠序列化来完成  
    // 自定义的类必须序列化  
    public String sayHello() throws RemoteException;  
}  

远程服务(实现)

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{  
  
      // 声明默认构造器  
     protected MyRemoteImpl() throws RemoteException {  
        super();  
    }  
      
    // 实现接口  
    public String sayHello() throws RemoteException {  
        return "Hello, world!";  
    }  
  
    public static void main(String[] args) {  
          
        try {  
            MyRemote service = new MyRemoteImpl();  
            // 用Naming.rebind()绑定到remiregistry   
            Naming.rebind("rmi://127.0.0.1/RemoteHello", service);  
        } catch (Exception e){  
            e.printStackTrace();  
        }  
    }  
}  

---------------------------------------------------------------------

客户端获取Stub对象


工作方式:

①客户到RMI registry中寻找

Name.lookup("rmi://127.0.0.1/RemoteHello");

②RMI registry返回Stub对象。

③客户调用stub的方法,就像stub就是真正的服务对象一样。


客户端代码:

import java.rmi.*;  
  
public class MyRemoteClient {  
    public static void main (String[] args) {  
        new MyRemoteClient().go();   
    }  
  
    public void go() {  
  
        try{  
            // 返回值是Object,所以要转换类型  
            // 需要ip地址或主机名,以及服务器绑定/重绑定时用的名称  
            MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/RemoteHello");   
  
            // 看起来和一般的方法调用没有区别!(除了必须注意RemoteException之外)  
            String s = service.sayHello();  
  
            System.out.println(s);  
  
        } catch (Exception e) {  
            e.printStackTrace();   
        }  
    }  
}  

对于RMI,程序员常犯的三个错误:



***************************华丽的分割线*************************************

ok,我们已经有了RMI的基础知识,现在回到代理模式

GumballMachine套用RMI框架


一、远程代理

GumballMachine服务端

①远程接口

public interface GumballMachineRemote extends Remote {//这个是对RMI有规定意义的,请遵循。 
    public int getCount() throws RemoteException;//声明的所有方法都要抛出RemoteException 
    public String getLocation() throws RemoteException;//并且要确定返回或者传入的变量都是基本类型或者可序列化的类型。 
    public State getState() throws RemoteException; 
}

这里我们原来的State就无法序列化,那么我们对它做一些修改:

public interface State extends Serializable { 
    public void insertQuarter(); 
    public void ejectQuarter(); 
    public void turnCrank(); 
    public void dispense(); 
}

② 制作远程的实现

它首先要继承UnicastRemoteObject类,以成为远程服务,其次它还要实现GumballMachineRemote 这个远程接口的实际功能。

public class GumballMachine 
        extends UnicastRemoteObject implements GumballMachineRemote  
{ 
    State soldOutState; 
    State noQuarterState; 
    State hasQuarterState; 
    State soldState; 
    State winnerState; 
    State state = soldOutState; 
    int count = 0; 
     String location;
    public GumballMachine(String location, int numberGumballs) throws RemoteException {//设计一个构造方法来抛出异常,因为超类就是这么做的。。 
        soldOutState = new SoldOutState(this); 
        noQuarterState = new NoQuarterState(this); 
        hasQuarterState = new HasQuarterState(this); 
        soldState = new SoldState(this); 
        winnerState = new WinnerState(this);
        this.count = numberGumballs; 
         if (numberGumballs > 0) { 
            state = noQuarterState; 
        }  
        this.location = location; 
    } 
    public void insertQuarter() { 
        state.insertQuarter(); 
    } 
    public void ejectQuarter() { 
        state.ejectQuarter(); 
    } 
    public void turnCrank() { 
        state.turnCrank(); 
        state.dispense(); 
    }
    void setState(State state) { 
        this.state = state; 
    } 
    void releaseBall() { 
        System.out.println("A gumball comes rolling out the slot..."); 
        if (count != 0) { 
            count = count - 1; 
        } 
    }
    public void refill(int count) { 
        this.count = count; 
        state = noQuarterState; 
    } 
    public int getCount() { 
        return count; 
    } 
    public State getState() { 
        return state; 
    } 
    public String getLocation() { 
        return location; 
    } 
    public State getSoldOutState() { 
        return soldOutState; 
    }
    public State getNoQuarterState() { 
        return noQuarterState; 
    }
    public State getHasQuarterState() { 
        return hasQuarterState; 
    }
    public State getSoldState() { 
        return soldState; 
    }
    public State getWinnerState() { 
        return winnerState; 
    } 
    public String toString() { 
        StringBuffer result = new StringBuffer(); 
        result.append("/nMighty Gumball, Inc."); 
        result.append("/nJava-enabled Standing Gumball Model #2004"); 
        result.append("/nInventory: " + count + " gumball"); 
        if (count != 1) { 
            result.append("s"); 
        } 
        result.append("/n"); 
        result.append("Machine is " + state + "/n"); 
        return result.toString(); 
    } 
}

③在RMI registry中注册

public class GumballMachineTestDrive { 
    public static void main(String[] args) { 
        GumballMachineRemote gumballMachine = null; 
        int count;
        if (args.length < 2) { 
            System.out.println("GumballMachine "); 
             System.exit(1); 
        }
        try { 
            count = Integer.parseInt(args[1]);
            gumballMachine =  
                new GumballMachine(args[0], count); 
            Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);//这句话是关键 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    } 
}
首先我们在命令行中启动rmiregistry,然后运行这个类,Server端就开始工作了。



GumballMachine客户端 开始了~~

④开始处理客户端


⑤Ok,万事俱备,只欠东风。来个测试类吧:

public class GumballMonitorTestDrive { 
    public static void main(String[] args) { 
        String[] location = {"rmi://santafe.mightygumball.com/gumballmachine", 
                             "rmi://boulder.mightygumball.com/gumballmachine", 
                             "rmi://seattle.mightygumball.com/gumballmachine"};  
        GumballMonitor[] monitor = new GumballMonitor[location.length]; 
        for (int i=0;i < location.length; i++) { 
            try { 
                   GumballMachineRemote machine =  
                        (GumballMachineRemote) Naming.lookup(location[i]); 
                   monitor[i] = new GumballMonitor(machine); 
                System.out.println(monitor[i]); 
            } catch (Exception e) { 
                e.printStackTrace(); 
            } 
        } 
        for(int i=0; i < monitor.length; i++) { 
            monitor[i].report(); 
        } 
    } 
}

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

  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的资源


二、虚拟代理

虚拟代理作为创建开销大的对象的代表。虚拟代理经常至到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。


显示CD封面的例子

略。

【why】

为什么需要代理模式?


【where & when】
应用的场合?时间?


【在Java世界的应用实例】

Spring 代理模式

【与其他模式的联系 & 区别】

与装饰者模式区别:

装饰模式的主要对象是为对象增加动作,比如 给对象增加 走路像鸭子,会游泳,会 嘎嘎 鸭子叫,使对象看上去就是一直鸭子。

代理模式是代表对象,不是装饰对象。

代表对象,不光是为对象加上动作,而是控制对象的访问,作为真正主题的替身,保护对象避免不必要的访问,

也可以避免在加载大对象的过程中GUI会挂起,或者隐藏主题在远程运行的事实。


【5分钟短剧】

经纪人的故事……



【扩展】

Java API 动态代理

InvocationHandler

*注:这个有空要好好研究下(2016-04-17)


你可以把InvocationHandler想成是代理收到方法调用后,请求做实际工作的对象。

其中只有一个invoke()方法,不管代理被调用的是何种方法,处理器被调用的一定是invoke()方法。

【适用场景】

 1、 远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
 2、 虚拟代理:通过使用过一个小的对象代理一个大对象。这样就可以减少系统的开销。
 3、 保护代理:用来控制对真实对象的访问权限。


【一句话总结】

当你看到代理模式四个字的时候,脑海中条件反射,应该会想起这个故事:

高老庄大小姐-悟空-八戒




【鸣谢】

http://blog.csdn.net/shuangde800/article/details/10208847

【设计模式】学习笔记15:代理模式(Proxy Pattern)


【续集】

http://blog.csdn.net/kakaxi_77/article/details/78646115

代理模式2-静态代理与动态代理


最后,老规矩-猴子镇楼:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值