设计模式: Proxy 代理模式
简介
目的 | 创建型 | 结构型 | 行为型 |
---|---|---|---|
类 | Factory Method 工厂方法 | Adapter 适配器 | Interpreter 解释器 Template Method 模版方法 |
对象 | Abstract Factory 抽象工厂 Builder 生成器 Prototype 原型 Singleton 单例 | Adapter 适配器 Bridge 桥接 Composite 组合 Decorator 装饰器 Facade 外观 Flyweight 享元 Proxy 代理 | Chain of Responsibility 职责链 Command 命令 Iterator 迭代器 Mediator 中介者 Memento 备忘录 Observer 观察者 State 状态 Strategy 策略 Visitor 访问者 |
参考
Design Patterns-Elements of Reusable Object-Oriented Software |
完整示例代码
正文
场景
先说说代理模式的应用场景。在 OOP 的编程思想当中,通常我们习惯将操作和关联数据封装成一个对象(Object),而我们操作的目标则是以对象为单位。然而很多时候我们可能并不希望直接访问对象,而是根据需要访问不同的代理对象(Proxy):
- 通过不同途径访问对象(二次请求、远程代理、编码) → \to → R e m o t e P r o x y 远 程 对 象 Remote Proxy \space 远程对象 RemoteProxy 远程对象
- 对象缓冲(附加信息、静态信息缓冲) → \to → V i r t u a l P r o x y 虚 拟 对 象 Virtual Proxy \space 虚拟对象 VirtualProxy 虚拟对象
- 检查、过滤使用者权限(访问控制) → \to → P r o t e c t i o n P r o x y 保 护 对 象 Protection Proxy \space 保护对象 ProtectionProxy 保护对象
当然代理对象还能有其他用途,然而不论基于什么目的,代理的本质在于:建立一个新的对象,包裹并代理目标对象的必要操作,并在真正调用目标对象方法过程中(调用前 or 后)加入额外的其他操作、检查等,这就是一种代理。
模式结构
- Subject 操作对象接口:定义目标对象的公共操作接口
- RealSubject 操作对象实体:真正执行核心操作的对象
- ProxySubject 操作对象代理:保存操作对象实体,并对实体的操作进行代理
代理模式的类型结构非常简单,我们对原本直接操作的对象(Real Subject)抽象出一个公共的操作接口(Subject),并定义一个新的**代理对象(Proxy)**用于代理一个对象实体的操作。
代码示例
下面给出一个极其简单的代码示例,本身并不代表任何功能,但是实现代理模式的精神
Subject 操作接口
package com.example.proxy.classic;
public interface Subject {
void f(); // method1
void g(); // method2
}
我们一共定义了两种操作接口
Real Subject 操作实体
首先我们先给出操作实体实现的具体操作
package com.example.proxy.classic;
public class SubjectImpl implements Subject {
@Override
public void f() {
System.out.println("invoke function f");
}
@Override
public void g() {
System.out.println("invoke function g");
}
}
Proxy Subject 操作代理
接下来的代理对象接受另一个操作对象作构造函数参数,这表示代理对象还能够多层嵌套的,我们调用一个代理对象的时候,调用的顺序可能如下图所示。
其他操作接口仅仅是对构造时保存的对象的操作进行代理,额外操作的体现是在执行前多输出加一个前缀。
package com.example.proxy.classic;
public class SubjectProxy implements Subject {
private Subject subject;
public SubjectProxy(Subject subject) {
this.subject = subject;
}
private void prefix() {
System.out.print("[Subject Proxy]");
}
@Override
public void f() {
prefix();
subject.f();
}
@Override
public void g() {
prefix();
subject.g();
}
}
测试代码
最后给出测试代码和输出
package com.example.proxy.classic;
import org.junit.Test;
import static org.junit.Assert.*;
public class SubjectTest {
@Test
public void test() {
Subject subject = new SubjectImpl();
Subject proxy = new SubjectProxy(subject);
subject.f();
subject.g();
proxy.f();
proxy.g();
}
}
invoke function f
invoke function g
[Subject Proxy]invoke function f
[Subject Proxy]invoke function g
我们建立了一个操作实体,并用它来创建了一个代理对象。前两个调用直接调用原对象的方法,可以看到直接输出两个字符串;后两次调用则是透过代理对象进行调用,与设想的一样,多了代理对象的前缀。
结语
代理模式本质上是对操作对象的一种保护,不论是访问远程对象或是需要进行访问管理控制,或是需要对对象的操作**附带额外信息、进行额外的操作(记录、编码)**等,简单来说就是对真正的对象操作进行一层包裹、一层封装。
同时代理模式也引出一个 OOP 编程设计中非常重要的一条思想:面对接口编程。我们必须接受面对接口编程的好处,仅仅只需要知道对象的模样(look like),而不是直接与对象的实际类型打交道,如此一来我们也就不需要区分操作对象的类型究竟是对象实体(Real Subject)还是代理对象(Proxy)。