Java设计模式:代理模式(一)

问题的提出

现在生活中,常常在微信朋友圈里面看到代购的信息,你想在国外买什么,香港买什么,但是又懒得自己过去,于是常常委托别人帮忙买奶粉买那啥的。这类问题的缘由是因为客户和原产地没有直接的接触,所以需要一个代理(代购)的第三者来实现间接引用。代理对象可以在客户端和目标对象间起到中介作用,而且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。代理模式是一种很好实现客户对象与代理对象分离的策略。其抽象UML图如下图

代理模式包含如下角色

ISubject:抽象主题角色,是一个接口。该接口是对象和它的代理公用的接口。

RealSubject:真实的主题角色,是实现主题接口的类。

Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

买电视为例

1)定义抽象主题-买电视

public interface ITV {
    public void buyTV();
}

2)定义实际主题-买电视过程

public class Buyer implements ITV {
    public void buyTV(){
        System.out.println("I have bought the TV by buyer proxy");
    }
}

3)定义代理

public class BuyerProxy implements ITV {
    private Buyer buyer;
    public BuyerProxy(Buyer buyer){
        this.buyer = buyer;
    }
    public void buyTV(){
        preProcess();
        buyer.buyTV();
        postProcess();
    }
    public void preProcess(){
        //询问客户需要的电视类型,价位等信息
    }
    public void postProcess(){
        //负责把电视送到客户家(售后服务)
    }
}

在这里,一开始看到这样的设计的时候。会有这样的疑问,代理和实际主题为什么都要实现相同的接口呢?而且接口方法的实现完全一样。当从代码段分析,确实感到很累赘。但是,结合实际想想,你需要代理者帮你买东西,那作为代理者,理所因当必须要有和你一样的功能:买东西!代理对象提供与真实对象相同的接口,以便在任何时刻都能替代真实对象。

代理模式最突出的特点:代理角色与实际主题角色有相同的父类接口。常用的代理方式有4类:虚拟代理,远程代理,计数代理,动态代理。

下面分别简单介绍一下这四种代理模式

一.虚拟代理

虚拟代理的关键思想是:如果需要创建的对象资源消耗较大,那就先创建一个消耗相对比较小的对象表示。真正完整的对象只有在需要时才会被创建。当用户请求一个“大”的对象时,虚拟代理在该对象真正被创建出来之前扮演着替身的角色;当对象被创建出来后,虚拟代理将用户请求直接委托给对象。

看下面这个例子,高校本科生科研信息查询功能设计

实验室数据库表字段说明

可以看出,第4,5个字段都是大段的文字,如果直接列出申请所有项目信息,一方面会花费比较多时间,另一方面界面也难设计,因为前三个字段较短,后面较长。良好的查询策略应该分为二级查询,第一级查询显示“账号,姓名,项目名称”三个字段,第二级查询时当鼠标选中表中某一个具体项目时,再进行一次数据库查询,得到项目完整信息。

Mysql数据表简单设计

    create table project(
        account varchar(20),
        name varchar(20),
        project varchar(20),
        content varchar(20),
        plan varchar(20)
    )

1)定义抽象主题接口IItem

public interface IItem {
    String getAccount();
    void setAccount(String s);
    String getName();
    void setName(String s);
    String getProject();
    void setProject(String s);
    String getContent();
    String getPlan();
    void itemFill() throws Exception;
}

注意这里content和plan字段只有getter方法,表明这两个字段的信息仅仅执行第二级查询时候才填充完整,由itemFill()方法完成

2)具体实现主题类RealItem

import BuildModel.Example.DbProc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * Created by lenovo on 2017/4/21.
 */
public class RealItem implements IItem {
    private String account;
    private String name;
    private String project;
    private String content;
    private String plan;

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getProject() {
        return project;
    }

    public void setProject(String project) {
        this.project = project;
    }

    public String getContent() {
        return content;
    }

    public String getPlan() {
        return plan;
    }

    public void itemFill() throws Exception{        //填充本项目content以及plan字段 第二级查询
        System.out.println(account);
        String strSQL = "select content,plan from project";
        DbProc dbProc = new DbProc();
        Connection conn = dbProc.connect();
        Statement stm = conn.createStatement();
        ResultSet rst = stm.executeQuery(strSQL);
        rst.next();

        content = rst.getString("content");         //填充content字段内容
        plan = rst.getString("plan");               //填充plan字段
        rst.close();
        stm.close();
        conn.close();
    }
}

ItemFill方法描述第二级查询时如何获得content,plan字段具体内容的过程,但是什么时候调用,怎么调用是由代理类完成。

3)代理主题类ProxyItem

public class ProxyItem implements IItem{
    private RealItem item;
    boolean bFill = false ;
    public ProxyItem(RealItem item){
        this.item = item;
    }

    public String getAccount() {
        return item.getAccount();
    }

    public void setAccount(String s) {
        item.setAccount(s);
    }

    public String getName() {
        return item.getName();
    }

    public void setName(String s) {
        item.setName(s);
    }

    public String getProject() {
        return item.getProject();
    }

    public void setProject(String s) {
        item.setProject(s);
    }

    public String getContent() {
        return item.getContent();
    }

    public String getPlan() {
        return item.getPlan();
    }

    public void itemFill() throws Exception {
        if(!bFill){
            item.itemFill();
        }
        bFill = true;
    }
}

初始bFil为false,调用一次item.itemFill方法后,完成对该字段的填充,最后把bFill置为true,所以无论调用代理对象itemFill多少次,只执行一次具体主题类对象的itemFill方法一次。

4)代理项目集合类ManageItems

public class ManageItems {
    Vector<ProxyItem> v = new Vector();                                 //代理项目集合
    public void firstSearch() throws Exception{
        String strSQL = "select account ,name ,project from project" ;//第一级查询SQL语句
        DbProc dbProc = new DbProc();
        Connection conn = dbProc.connect();
        Statement stm = conn.createStatement();
        ResultSet rst = stm.executeQuery(strSQL);                      //获得第一级查询集合
        while(rst.next()){
            ProxyItem obj = new ProxyItem(new RealItem());
            obj.setAccount(rst.getString("account"));
            obj.setName(rst.getString("name"));
            obj.setProject(rst.getString("project"));
            v.add(obj);
        }
        rst.close();
        stm.close();
        conn.close();
    }
}

对于数据库应用来说,往往是对记的集合进行操作。若要用到代理模式,一般需要一个代理基恩类如ProxyItem,代理集合管理类如ManageItems。

5)界面显示以及消息映射类UFrame

package BridgeModel.Proxy.virtualProxy;

import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Vector;

/**
 * Created by lenovo on 2017/4/21.
 */
public class UFrame extends JFrame implements MouseListener {
    ManageItems manage = new ManageItems();
    JTable table;
    JTextArea t = new JTextArea();
    JTextArea t2 = new JTextArea();
    public void init() throws Exception{
        setLayout(null);
        manage.firstSearch();

        String title[] = {"账号","姓名","项目名称"};
        String data[][] = null;
        Vector<ProxyItem> v = manage.v;
        data = new String[v.size()][title.length];
        for(int i=0;i<v.size();i++){
            ProxyItem proxyItem = v.get(i);
            data[i][0] = proxyItem.getAccount();
            data[i][1] = proxyItem.getName();
            data[i][2] = proxyItem.getProject();
        }
        table = new JTable(data,title);
        JScrollPane pane = new JScrollPane(table);
        pane.setBounds(10,10,200,340);
        JLabel label = new JLabel("项目主要内容");
        JLabel label2 = new JLabel("计划安排");
        label.setBounds(230,5,100,20);
        t.setBounds(230,40,200,100);
        label2.setBounds(230,160,100,20);
        t2.setBounds(230,195,200,100);

        add(pane);
        add(label);
        add(t);
        add(label2);
        add(t2);

        table.addMouseListener(this);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(500,350);
        setVisible(true);
    }
    public void mouseClicked(MouseEvent event){     //进行二级查询
        try{
            int n = table.getSelectedRow();
            if(n>=0){
                ProxyItem item = manage.v.get(n);
                item.itemFill();
                t.setText(item.getContent());
                t2.setText(item.getPlan());
            }
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
    }

    public void mousePressed(MouseEvent e) {

    }

    public void mouseReleased(MouseEvent e) {

    }

    public void mouseEntered(MouseEvent e) {

    }

    public void mouseExited(MouseEvent e) {

    }

    public static void main(String[] args) throws Exception{
        new UFrame().init();
    }
}

6)数据库处理封装类

public class DbProc {
    private String strDriver = "com.mysql.jdbc.Driver";
    private String strDb = "jdbc:mysql://localhost:3306/buildModel";
    private String strUser = "root";
    private String strPwd = "root";         //注意测试时候strPwd要加上自己本地mysql的账户密码
    private Connection conn;
    public Connection connect() throws Exception{
        Class.forName(strDriver);
        conn = DriverManager.getConnection(strDb,strUser,strPwd);
        return conn;
    }
    public int executeUpdate(String strSQL) throws Exception{
        Statement stm = conn.createStatement();
        int n = stm.executeUpdate(strSQL);
        stm.close();
        return n;
    }
    public List executeQuery(String strSQL) throws Exception{
        List l = new Vector();
        Statement stm = conn.createStatement();
        ResultSet rst = stm.executeQuery(strSQL);
        ResultSetMetaData rsmd = rst.getMetaData();
        while(rst.next()){
            Vector unit = new Vector();
            for(int i=1;i<=rsmd.getColumnCount();i++){
                unit.add(rst.getString(i));
            }
            l.add(unit);
        }
        return l;
    }
    public void close() throws Exception{
        conn.close();
    }
}

二.远程代理

远程代理的含义是:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中。也就是说,远程对象驻留于服务器上,客户机请求调用远程对象调用相应方法,执行完毕后,结果由服务器返回给客户端。基本框架如下

虚拟代理一般有一个代理,而远程代理包括两个代理:客户端通信代理与服务器端通信代理,编程时需要考虑这两部分。实际上JDK已经提供了远程服务器端通信代理,如RMI(Remote Method Invocation)远程方法调用。这意味着我们可以不用考虑远程代理编码,只需要编写普通代码即可。

为了更好地理解远程代理程序作用,下面编写一个简单的RMI代理模拟系统,客户端输入字符串数学表达式,只含+,-运算。服务器计算表达式值并返回给客户端。

1)创建服务器工程

1.定义抽象主题远程接口ICalc

public interface ICalc {
    float calc(String s) throws Exception;
}

2.定义具体远程主题实现ServerCalc

public class ServerCalc implements ICalc {
    public float calc(String s) throws Exception{
        s += "+0";
        float result = 0;
        float value = 0;
        char opcur = ‘+‘;
        char opnext;
        int start = 0;
        if(s.charAt(0)==‘-‘){
            opcur = ‘-‘;
            start = 1;
        }
        for(int i=start;i<s.length();i++){
            if(s.charAt(i)==‘+‘ || s.charAt(i)==‘-‘){
                opnext = s.charAt(i);
                value = Float.parseFloat(s.substring(start,i));
                switch (opcur){
                    case ‘+‘: result += value;  break;
                    case ‘-‘: result -= value;  break;
                }
                start = i+1;
                opcur = opnext;
                i = start;
            }
        }
        return result;
    }
}

3.定义服务器端远程代理类ServerProxy

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by lenovo on 2017/4/21.
 */
public class ServerProxy extends Thread {
    ServerCalc obj;
    public ServerProxy(ServerCalc obj){
        this.obj = obj;
    }
    public void run(){
        try{
            ServerSocket s = new ServerSocket(4000);
            Socket socket = s.accept();
            while(socket != null){
                ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
                String method = (String)in.readObject();

                if(method.equals("calc")){
                    String para = (String)in.readObject();
                    float f = obj.calc(para);
                    out.writeObject(new Float(f));
                    out.flush();
                }
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

一般服务器一定是支持多线程的。所以ServerProxy从Thread派生。RMI服务器段程序是通过网络通信,所以用Socket接口。Run中封装了远程代理功能。当客户端申请远程执行calc(String expression)方法时,in首先先读取方法名method,若method为字符串“calc”,in继续读取网络获得表达式expression。然后调用ServerCalc类中的calc()方法,计算expression的value,最后传回给客户端。

2)创建客户端工程RmiClientSimu

1.定义抽象主题远程接口ICalc (这里和Server的ICalc一样,就不再列出代码了)

2.定义服务器远程代理类ClientProxy

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

/**
 * Created by lenovo on 2017/4/21.
 */
public class ClientProxy implements ICalc {
    Socket socket;
    public ClientProxy() throws Exception{
        socket = new Socket("localhost",4000);
    }
    public float calc(String expression) throws Exception{
        ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
        out.writeObject("calc");
        out.writeObject(expression);
        Float value = (Float)in.readObject();
        return value.floatValue();
    }
}

测试代码

public class ClientTest {
    public static void main(String[] args) throws Exception{
        ICalc obj = new ClientProxy();
        float value = obj.calc("23+2+3");
        System.out.println("value = " + value);
    }
}
public class ServerTest {
    public static void main(String[] args) {
        ServerCalc obj = new ServerCalc();
        ServerProxy spobj = new ServerProxy(obj);
        spobj.start();
    }
}

先运行ServerTest,同时再运行ClientTest,再Client端窗口就会得到服务器端返回来的计算结果 value=28!

有人或许会迷惑,那这样与直接处理从客户端获得表达式求解再发回去有什么区别?当然,这个例子可能不太能体现远程代理的好处。但是,我们打个比方,你寄快递的时候,你只需要给包裹给快递站并签好快递单就好了,根本不需要去了解快递公司是用什么路线,通过什么方式去寄过去,只要能到达目的地就好了对吧?远程代理的作用其实好比于快递公司!这样就理解了对吧。至于JDK提供的RMI接口,这里就不再详叙了。

今天的节目就到这里,感谢大家收看!

下回预告:计数代理与动态代理

时间: 2024-10-22 00:15:16

Java设计模式:代理模式(一)的相关文章

一起学java设计模式--代理模式(结构型模式)

代理模式 应用软件所提供的桌面快捷方式是快速启动应用程序的代理,桌面快捷方式一般使用一张小图片来表示(Picture),通过调用快捷方式的run()方法将调用应用软件(Application)的run()方法.使用代理模式模拟该过程,绘制类图并编程实现. package ProxyPattern; interface Software { void run(); } class Application implements Software { public void run() { Syste

Java设计模式-代理模式之动态代理(附源码分析)

Java设计模式-代理模式之动态代理(附源码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的区别就是:动态代理是在运行时刻动态的创建出代理类及其对象.上篇中的静态代理是在编译的时候就确定了代理类具体类型,如果有多个类需要代理,那么就得创建多个.还有一点,如果Subject中新增了一个方法,那么对应的实现接口的类中也要相应的实习该方法,不符合设计模式原则. 动态代理的做法:在运行时刻,可以动态创建出一个实现了多个接口的代理类.每个代理类的对象都会关联一个表示内部处理

Java设计模式-代理模式之动态代理(附源代码分析)

Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代理是在编译的时候就确定了代理类详细类型.假设有多个类须要代理.那么就得创建多个. 另一点,假设Subject中新增了一个方法,那么相应的实现接口的类中也要相应的实现这些方法. 动态代理的做法:在执行时刻.能够动态创建出一个实现了多个接口的代理类.每一个代理类的对象都会关联一个表示内部处理逻辑的Inv

Java设计模式-代理模式之静态代理

Java设计模式-代理模式之静态代理 概念 为另一个对象提供一个替身或占位符以提供对这个对象的访问,使用代理模式创建代表对象,让代表对象控制某对象的访问,被代理对象可以是远程的对象.创建开销大的对象或需要安全控制的对象 远程代理控制访问远程对象 虚拟代理控制访问创建开销大的资源 保护代理基于权限控制对资源的访问 看如下的类图: 仔细看上面的类图,首先是Subject它为RealSubject和Proxy提供了接口,通过实现同一个接口,Proxy在RealSubject出现的地方取代它,这点和适配

Java设计模式の代理模式

目录  代理模式 1.1.静态代理   1.2.动态代理 1.3.Cglib代理 代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法 举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的

Java设计模式——代理模式

前言: 上一篇说到了策略模式.单类图上来说,它和本篇要说的代理模式还真是有些像似.都需要一个公共的接口,还有一些实现类.代理类(包装类)封装了一个接口对象,提供客户端调用.这些都很类似.不过,有一个细节需要我们注意一下,那就是这里的代理类也需要去继承这里的公共接口.而在策略模式中,包装类则不需要这么做. 概述: 代理模式就是定义一个原对象的代理对象,来帮助原对象和系统之外的业务作沟通.也就是说,如果我们不能直接或是不愿直接去使用原对象,那么我们就可以使用创建一个原对象的代理来进行操作. 本文链接

Java设计模式—代理模式

代理模式(Proxy Pattern)也叫做委托模式,是一个使用率非常高的模式. 定义如下:     为其他对象提供一种代理以控制对这个对象的访问. 个人理解:        代理模式将原类进行封装,客户端不能直接找到原类,必须通过代理角色.即代理是原类的一个替身,客户端要找原类,统统找代理就可以搞定.明星和经纪人就是一种代理模式. 通用类图如下: 角色定义: ● Subject 抽象主题角色     抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求. ● Real S

java设计模式--代理模式

代理模式 proxy:为其他对象提供一种代理,并以控制对这个对象的访问,好比经纪人和明星之间的关系,经纪人就是明星的代理类.简单的就是在方法调用前后做处理,AOP思想,好处就是不改变原来类方法的基础上,动态的添加其他方法. 代理模式的3个角色 1.抽象角色2.真实角色 3.代理角色 1.静态代理 代理类调用被代理类的方法. 2.动态代理---比较常用 public interface People { void eat(); } public class Zhangsan implements

java 设计模式 - 代理模式 - 静态代理

代理模式:使用环境无法直接使用对象A(委托对象),可以使用对象B(代理对象)调用对象A已达到直接调用对象A的效果,就成为代理模式. 其中A为委托对象,B为代理对象. 静态代理的优缺点: 优点: 1.代码只管,可以清楚理解委托雷以及代理类. 2.在编译期加入,提前就指定好了委托类,代理类,效率高. 缺点: 1.静态代理很麻烦,需要大量的代理类,不易于代码维护等 当我们有多个目标对象需要代理时,我就需要建立多个代理类,改变原有的代码,改的多了就很有可能出问题,必须要重新测试. 2.重复的代码会出现在

23种设计模式----------代理模式(三) 之 动态代理模式

(上一篇)种设计模式----------代理模式(二) 当然代理模式中,用的最广泛的,用的最多的是  动态代理模式. 动态代理:就是实现阶段不用关系代理是哪个,而在运行阶段指定具体哪个代理. 抽象接口的类图如下: --图来自设计模式之禅 所以动态代理模式要有一个InvocationHandler接口 和 GamePlayerIH实现类.其中 InvocationHandler是JD提供的动态代理接口,对被代理类的方法进行代理. 代码实现如下 抽象主题类或者接口: 1 package com.ye