设计模式——8. 代理模式

1. 说明

代理模式(Proxy Pattern)是一种结构型设计模式,它允许一个对象(代理对象)充当另一个对象(真实对象)的接口,以控制对该对象的访问。代理对象在访问真实对象时可以执行一些额外的操作,例如权限验证、懒加载、缓存、日志记录等,而无需修改真实对象的代码。

代理模式的主要目的是为了控制对对象的访问,以提供更加灵活和安全的方式来处理对象。代理模式通常涉及以下几种角色:

  1. Subject(主题):定义了真实对象和代理对象之间的共同接口,客户端通过这个接口访问真实对象。主题可以是一个抽象类或接口。
  2. Real Subject(真实主题):实际执行业务逻辑的类,它实现了主题接口。代理对象将请求传递给真实主题,由真实主题执行实际操作。
  3. Proxy(代理):充当了真实主题和客户端之间的中介,实现了主题接口。它持有对真实主题的引用,并在必要时执行一些额外的操作,然后将请求传递给真实主题。

代理模式有多种变体,包括静态代理和动态代理。静态代理是在编译时创建代理类,而动态代理是在运行时动态生成代理对象。

代理模式可以帮助解耦客户端与真实对象之间的关系,同时也提供了一种机制来添加新的功能或修改现有功能,而无需修改真实对象的代码。

2. 使用的场景

  1. 远程代理(Remote Proxy)
  • 当对象位于远程服务器上时,代理对象可以在客户端和服务器之间充当中介,以隐藏底层网络通信的细节。
  • 用于实现远程方法调用(RPC)或远程对象访问。
  1. 虚拟代理(Virtual Proxy)
  • 用于延迟创建开销较大的对象,直到真正需要使用它们时才进行实际创建,以提高性能和节省资源。
  • 常用于加载大型图像或文档的情况,只有在用户请求查看时才加载真实对象。
  1. 安全代理(Security Proxy)
  • 用于控制对对象的访问,例如,限制某些用户或角色对对象的访问权限。
  • 可以用于实现身份验证、授权和访问控制。
  1. 缓存代理(Cache Proxy)
  • 用于缓存对象的一部分或全部数据,以提高访问速度。
  • 常用于处理频繁访问的数据,减少对底层资源的请求次数。
  1. 日志记录(Logging Proxy)
  • 用于记录对象的访问日志,用于调试、性能分析或审计。
  • 可以记录方法调用的参数、返回值和执行时间等信息。
  1. 智能引用代理(Smart Reference Proxy)
  • 用于对真实对象的引用计数,以确保只有在不再需要时才会释放资源。
  • 常用于管理大量资源有限的对象,如数据库连接池。
  1. 动态代理(Dynamic Proxy)
  • 在运行时动态生成代理对象,通常使用反射机制来实现。
  • 常用于AOP(面向切面编程)等场景,允许在方法调用前后添加额外的逻辑。
  1. 多线程代理(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. 实现要素

代理模式的实现要素包括以下几个关键元素:

  1. Subject(主题):Subject 是一个接口或抽象类,定义了真实对象和代理对象之间共同的接口,通常包括了真实对象和代理对象都必须实现的方法。
  2. Real Subject(真实主题):Real Subject 是实际执行业务逻辑的类,它实现了 Subject 接口。真实主题包含了客户端感兴趣的具体功能。
  3. 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 接口,它负责实际播放音乐文件。

要求:

  1. 创建一个代理类 ProxyMusicPlayer,它实现了 MusicPlayer 接口并包含一个 RealMusicPlayer 对象,用于代理音乐播放器的操作。
  2. 代理类的主要作用是在需要时创建真实音乐播放器对象,并在客户端请求时转发这些请求给真实对象。

请编程实现上述情景,可以用任意编程语言,包括 MusicPlayer 接口、RealMusicPlayer 类和 ProxyMusicPlayer 类。在客户端代码中,使用代理类来播放音乐。

你可以在评论区里或者私信我回复您的答案,这样我或者大家都能帮你解答,期待着你的回复~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guohuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值