1. 说明
代理模式(Proxy Pattern)是一种结构型设计模式,它允许一个对象(代理对象)充当另一个对象(真实对象)的接口,以控制对该对象的访问。代理对象在访问真实对象时可以执行一些额外的操作,例如权限验证、懒加载、缓存、日志记录等,而无需修改真实对象的代码。
代理模式的主要目的是为了控制对对象的访问,以提供更加灵活和安全的方式来处理对象。代理模式通常涉及以下几种角色:
- Subject(主题):定义了真实对象和代理对象之间的共同接口,客户端通过这个接口访问真实对象。主题可以是一个抽象类或接口。
- Real Subject(真实主题):实际执行业务逻辑的类,它实现了主题接口。代理对象将请求传递给真实主题,由真实主题执行实际操作。
- Proxy(代理):充当了真实主题和客户端之间的中介,实现了主题接口。它持有对真实主题的引用,并在必要时执行一些额外的操作,然后将请求传递给真实主题。
代理模式有多种变体,包括静态代理和动态代理。静态代理是在编译时创建代理类,而动态代理是在运行时动态生成代理对象。
代理模式可以帮助解耦客户端与真实对象之间的关系,同时也提供了一种机制来添加新的功能或修改现有功能,而无需修改真实对象的代码。
2. 使用的场景
- 远程代理(Remote Proxy)
- 当对象位于远程服务器上时,代理对象可以在客户端和服务器之间充当中介,以隐藏底层网络通信的细节。
- 用于实现远程方法调用(RPC)或远程对象访问。
- 虚拟代理(Virtual Proxy)
- 用于延迟创建开销较大的对象,直到真正需要使用它们时才进行实际创建,以提高性能和节省资源。
- 常用于加载大型图像或文档的情况,只有在用户请求查看时才加载真实对象。
- 安全代理(Security Proxy)
- 用于控制对对象的访问,例如,限制某些用户或角色对对象的访问权限。
- 可以用于实现身份验证、授权和访问控制。
- 缓存代理(Cache Proxy)
- 用于缓存对象的一部分或全部数据,以提高访问速度。
- 常用于处理频繁访问的数据,减少对底层资源的请求次数。
- 日志记录(Logging Proxy)
- 用于记录对象的访问日志,用于调试、性能分析或审计。
- 可以记录方法调用的参数、返回值和执行时间等信息。
- 智能引用代理(Smart Reference Proxy)
- 用于对真实对象的引用计数,以确保只有在不再需要时才会释放资源。
- 常用于管理大量资源有限的对象,如数据库连接池。
- 动态代理(Dynamic Proxy)
- 在运行时动态生成代理对象,通常使用反射机制来实现。
- 常用于AOP(面向切面编程)等场景,允许在方法调用前后添加额外的逻辑。
- 多线程代理(Multithreaded Proxy)
- 用于控制多线程访问共享资源的并发情况,确保线程安全性。
- 常用于实现线程安全的单例模式。
这些场景都展示了代理模式的灵活性和适用性。代理模式可以帮助解耦客户端与真实对象之间的关系,同时也提供了一种机制来添加新的功能或修改现有功能,而无需修改真实对象的代码。
3. 应用例子
以下是使用 Python 实现的代理模式示例,其中模拟了一个简单的图片加载器,使用代理来控制图片的加载和显示:
# 主题接口,定义了加载和显示图片的方法
class ImageSubject:
def load(self):
pass
def display(self):
pass
# 真实主题,负责加载和显示图片
class RealImage(ImageSubject):
def __init__(self, filename):
self.filename = filename
self.load()
def load(self):
print(f"加载图片: {self.filename}")
def display(self):
print(f"显示图片: {self.filename}")
# 代理,用于控制图片的加载和显示
class ImageProxy(ImageSubject):
def __init__(self, filename):
self.filename = filename
self.real_image = None
def load(self):
if self.real_image is None:
self.real_image = RealImage(self.filename)
else:
print(f"使用缓存的图片: {self.filename}")
def display(self):
if self.real_image is not None:
self.real_image.display()
else:
print("图片未加载")
# 客户端代码
if __name__ == "__main__":
# 使用代理加载和显示图片
image_proxy = ImageProxy("example.jpg")
image_proxy.display() # 第一次加载图片
image_proxy.display() # 使用缓存的图片
# 直接使用真实主题加载和显示图片
real_image = RealImage("example.jpg")
real_image.display()
在这个示例中:
- ImageSubject 是主题接口,定义了图片加载和显示的方法。
- RealImage 是真实主题,实际执行图片加载和显示的类。
- ImageProxy 是代理,它实现了 ImageSubject 接口,但在加载图片时会检查是否已经加载过,如果没有加载过就创建真实主题对象,否则使用缓存的对象。
在客户端代码中,我们首先使用代理对象来加载和显示图片。第一次加载图片时,代理会创建真实主题对象进行加载。后续再次加载同一图片时,代理会使用缓存的真实主题对象,不再创建新对象。然后,我们直接使用真实主题加载和显示图片,没有代理的干预。
4. 实现要素
代理模式的实现要素包括以下几个关键元素:
- Subject(主题):Subject 是一个接口或抽象类,定义了真实对象和代理对象之间共同的接口,通常包括了真实对象和代理对象都必须实现的方法。
- Real Subject(真实主题):Real Subject 是实际执行业务逻辑的类,它实现了 Subject 接口。真实主题包含了客户端感兴趣的具体功能。
- Proxy(代理):Proxy 类充当了真实主题和客户端之间的中介,它实现了 Subject 接口,持有对真实主题的引用。Proxy 对象在执行真实主题的操作时可以添加额外的逻辑,例如权限验证、懒加载、缓存等。
5. UML图
+-------------+ +--------------+ +-------------------+
| Subject | <--- | Proxy | <--- | Real Subject |
+-------------+ +--------------+ +-------------------+
| | | | | |
| +operation()| | +operation() | | +operation() |
| | | | | |
+-------------+ +--------------+ +-------------------+
在这个 UML 类图中:
- Subject 定义了真实对象和代理对象之间的接口,其中包含一个或多个操作方法,这些操作方法是客户端调用的入口。
- Proxy 类实现了 Subject 接口,同时持有对 Real Subject 的引用。它可以在执行操作方法时添加额外的逻辑。
- Real Subject 类实现了 Subject 接口,包含了真实的业务逻辑。
6. Java/golang/javascrip/C++ 等语言实现方式
6.1 Java实现
上述例子用Java语言实现示例如下:
// 主题接口,定义了加载和显示图片的方法
interface ImageSubject {
void load();
void display();
}
// 真实主题,负责加载和显示图片
class RealImage implements ImageSubject {
private String filename;
public RealImage(String filename) {
this.filename = filename;
load();
}
public void load() {
System.out.println("加载图片: " + filename);
}
public void display() {
System.out.println("显示图片: " + filename);
}
}
// 代理,用于控制图片的加载和显示
class ImageProxy implements ImageSubject {
private String filename;
private RealImage realImage;
public ImageProxy(String filename) {
this.filename = filename;
}
public void load() {
if (realImage == null) {
realImage = new RealImage(filename);
} else {
System.out.println("使用缓存的图片: " + filename);
}
}
public void display() {
if (realImage != null) {
realImage.display();
} else {
System.out.println("图片未加载");
}
}
}
// 客户端代码
public class ProxyPatternExample {
public static void main(String[] args) {
// 使用代理加载和显示图片
ImageSubject imageProxy = new ImageProxy("example.jpg");
imageProxy.display(); // 第一次加载图片
imageProxy.display(); // 使用缓存的图片
// 直接使用真实主题加载和显示图片
ImageSubject realImage = new RealImage("example.jpg");
realImage.display();
}
}
6.2 Golang实现
package main
import "fmt"
// 主题接口,定义了加载和显示图片的方法
type ImageSubject interface {
Load()
Display()
}
// 真实主题,负责加载和显示图片
type RealImage struct {
filename string
}
func NewRealImage(filename string) *RealImage {
return &RealImage{filename}
}
func (ri *RealImage) Load() {
fmt.Printf("加载图片: %s\n", ri.filename)
}
func (ri *RealImage) Display() {
fmt.Printf("显示图片: %s\n", ri.filename)
}
// 代理,用于控制图片的加载和显示
type ImageProxy struct {
filename string
realImage *RealImage
}
func NewImageProxy(filename string) *ImageProxy {
return &ImageProxy{filename: filename}
}
func (ip *ImageProxy) Load() {
if ip.realImage == nil {
ip.realImage = NewRealImage(ip.filename)
} else {
fmt.Printf("使用缓存的图片: %s\n", ip.filename)
}
}
func (ip *ImageProxy) Display() {
if ip.realImage != nil {
ip.realImage.Display()
} else {
fmt.Println("图片未加载")
}
}
func main() {
// 使用代理加载和显示图片
imageProxy := NewImageProxy("example.jpg")
imageProxy.Display() // 第一次加载图片
imageProxy.Display() // 使用缓存的图片
// 直接使用真实主题加载和显示图片
realImage := NewRealImage("example.jpg")
realImage.Display()
}
6.3 Javascript实现
// 主题接口,定义了加载和显示图片的方法
class ImageSubject {
load() {}
display() {}
}
// 真实主题,负责加载和显示图片
class RealImage extends ImageSubject {
constructor(filename) {
super();
this.filename = filename;
this.load();
}
load() {
console.log(`加载图片: ${this.filename}`);
}
display() {
console.log(`显示图片: ${this.filename}`);
}
}
// 代理,用于控制图片的加载和显示
class ImageProxy extends ImageSubject {
constructor(filename) {
super();
this.filename = filename;
this.realImage = null;
}
load() {
if (!this.realImage) {
this.realImage = new RealImage(this.filename);
} else {
console.log(`使用缓存的图片: ${this.filename}`);
}
}
display() {
if (this.realImage) {
this.realImage.display();
} else {
console.log("图片未加载");
}
}
}
// 客户端代码
const imageProxy = new ImageProxy("example.jpg");
imageProxy.display(); // 第一次加载图片
imageProxy.display(); // 使用缓存的图片
const realImage = new RealImage("example.jpg");
realImage.display();
6.4 C++实现
#include <iostream>
#include <string>
// 主题接口,定义了加载和显示图片的方法
class ImageSubject {
public:
virtual void load() = 0;
virtual void display() = 0;
};
// 真实主题,负责加载和显示图片
class RealImage : public ImageSubject {
private:
std::string filename;
public:
RealImage(const std::string& filename) : filename(filename) {
load();
}
void load() override {
std::cout << "加载图片: " << filename << std::endl;
}
void display() override {
std::cout << "显示图片: " << filename << std::endl;
}
};
// 代理,用于控制图片的加载和显示
class ImageProxy : public ImageSubject {
private:
std::string filename;
RealImage* realImage;
public:
ImageProxy(const std::string& filename) : filename(filename), realImage(nullptr) {}
void load() override {
if (!realImage) {
realImage = new RealImage(filename);
} else {
std::cout << "使用缓存的图片: " << filename << std::endl;
}
}
void display() override {
if (realImage) {
realImage->display();
} else {
std::cout << "图片未加载" << std::endl;
}
}
~ImageProxy() {
if (realImage) {
delete realImage;
}
}
};
// 客户端代码
int main() {
// 使用代理加载和显示图片
ImageSubject* imageProxy = new ImageProxy("example.jpg");
imageProxy->display(); // 第一次加载图片
imageProxy->display(); // 使用缓存的图片
// 直接使用真实主题加载和显示图片
ImageSubject* realImage = new RealImage("example.jpg");
realImage->display();
// 清理资源
delete imageProxy;
delete realImage;
return 0;
}
7. 练习题
假设你正在设计一个音乐播放器应用程序,其中有一个 MusicPlayer 接口定义了音乐播放器的基本功能,包括 play(播放音乐)、pause(暂停音乐)和 stop(停止音乐)。你还有一个 RealMusicPlayer 类实现了 MusicPlayer 接口,它负责实际播放音乐文件。
要求:
- 创建一个代理类 ProxyMusicPlayer,它实现了 MusicPlayer 接口并包含一个 RealMusicPlayer 对象,用于代理音乐播放器的操作。
- 代理类的主要作用是在需要时创建真实音乐播放器对象,并在客户端请求时转发这些请求给真实对象。
请编程实现上述情景,可以用任意编程语言,包括 MusicPlayer 接口、RealMusicPlayer 类和 ProxyMusicPlayer 类。在客户端代码中,使用代理类来播放音乐。
你可以在评论区里或者私信我回复您的答案,这样我或者大家都能帮你解答,期待着你的回复~