问题的提出

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

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

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

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

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

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...

  2. JAVA 设计模式 代理模式

    用途 代理模式 (Proxy) 为其他对象提供一种代理以控制对这个对象的访问. 代理模式是一种结构型模式. 结构

  3. Java设计模式の代理模式

    目录  代理模式 1.1.静态代理   1.2.动态代理 1.3.Cglib代理 代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是 ...

  4. Java设计模式 - 代理模式

    1.什么是代理模式: 为另一个对象提供一个替身或占位符以访问这个对象. 2.代理模式有什么好处: (1)延迟加载 当你需要从网络上面查看一张很大的图片时,你可以使用代理模式先查看它的缩略图看是否是自己 ...

  5. Java设计模式—代理模式

    代理模式(Proxy Pattern)也叫做委托模式,是一个使用率非常高的模式. 定义如下:     为其他对象提供一种代理以控制对这个对象的访问. 个人理解:        代理模式将原类进行封装, ...

  6. Java设计模式——代理模式实现及原理

    简介 Java编程的目标是实现现实不能完成的,优化现实能够完成的,是一种虚拟技术.生活中的方方面面都可以虚拟到代码中.代理模式所讲的就是现实生活中的这么一个概念:中介. 代理模式的定义:给某一个对象提 ...

  7. Java设计模式-代理模式(Proxy)

    其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你 ...

  8. Java设计模式--代理模式+动态代理+CGLib代理

    静态代理 抽象主题角色:声明真实主题和代理主题的共同接口. 代理主题角色:代理主题内部含有对真实主题的引用,从而在任何时候操作真实主题对象:代理主题提供一个与真实主题相同的接口,以便在任何时候都可以代 ...

  9. Java设计模式——代理模式

    public interface People { public void work(); } public class RealPeople implements People { public v ...

  10. Java 之 设计模式——代理模式

    设计模式——代理模式 一.概述 1.代理模式 (1)真实对象:被代理的对象 (2)代理对象:代理真实对象的 (3)代理模式:代理对象代理真实对象,达到增强真实对象功能的目的 二.实现方式 1.静态代理 ...

随机推荐

  1. Maven进阶宝典

    前言: 团队在开发过程中用的是maven项目,由于对maven的一些打包流程以及相关参数配置不是太了解,因此应大家的需求做一下maven的讲解,为了不误导大家,看了很多相关资料,自己也实验了一下,就把 ...

  2. linux文件系统详解

    最近在做磁盘性能优化,需要结合文件系统原理去思考优化方向,因此借此机会进一步加深了对文件系统的认识.在看这篇文章之前,建议先看下前面一篇关于磁盘工作原理的解读.下面简单总结一些要点分享出来: 一.文件 ...

  3. JSOI2015 一轮省选 个人题解与小结

    T1: 题目大意:现有一个以1为根节点的树,要求从1开始出发,经过下面的点然后最终要回到根节点.同时除了根节点之外各点均有一个权值(即受益,每个点上的收益只能拿一次,且经过的话必须拿),同时除了根节点 ...

  4. swift -- 构造/析构函数

     一.构造函数 //当一个类实例化一个对象时候,第一个调用的方法 class Student { //属性 var name = "ser" let age : Int //1.重 ...

  5. django进阶补充

    前言: 这篇博客对上篇博客django进阶作下补充. 一.效果图 前端界面较简单(丑),有两个功能: 从数据库中取出书名 eg: 新书A 在form表单输入书名,选择出版社,选择作者(多选),输入完毕 ...

  6. angularjs fileUpload

    文件上传一直是我不熟悉的地方,<a href='https://github.com/nervgh/angular-file-upload/wiki/Module-API'>官网解释的例子 ...

  7. DataTable的AcceptChanges()方法和DataRow的RowState属性

    这个属性是一个只读属性的枚举类型,一共有五个值,Detached,Unchanged,Added,Deleteed,Modified, 属性名 值 备注 Detached 1 已创建该行,但是该行不属 ...

  8. Spring:利用PerformanceMonitorInterceptor来协助应用性能优化

    前段时间对公司产品做性能优化,如果单依赖于测试,进度就会很慢.所以就通过对代码的方式来完成,并以此来加快项目进度.具体的执行方案自然就是要知道各个业务执行时间,针对业务来进行优化. 因为项目中使用了S ...

  9. Appium 解决锁屏截屏问题(java篇)

    今天有个小伙伴问我,怎么把锁屏进行解锁操作?   A.思路在初始化driver后,加入等待判断是否有锁屏(元素)(记得要加入等待) B.如果有就进行解锁,就一般的输入数字密码然后进行解锁(当然了你要知 ...

  10. SQL Server 获取本周,本月,本年等时间内记录

    datediff(week,zy_time,getdate())=0 //查询本周 datediff(month,zy_time,getdate())=0 //查询本月 本季:select * fro ...