• 设计模式——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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在这个示例中:

    • 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()      |
          |             |        |              |        |                   |
          +-------------+        +--------------+        +-------------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这个 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    6.4 C++实现

    #include 
    #include 
    
    // 主题接口,定义了加载和显示图片的方法
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    7. 练习题

    假设你正在设计一个音乐播放器应用程序,其中有一个 MusicPlayer 接口定义了音乐播放器的基本功能,包括 play(播放音乐)、pause(暂停音乐)和 stop(停止音乐)。你还有一个 RealMusicPlayer 类实现了 MusicPlayer 接口,它负责实际播放音乐文件。

    要求:

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

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

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

  • 相关阅读:
    机器视觉学习(三)—— 保存视频流
    APP网站如何防止短信验证码接口被攻击
    01 导论【计量经济学及stata应用】
    分类预测 | MATLAB实现XGBoost极限梯度提升树多特征分类预测
    java计算机毕业设计高校网上报销系统源码+mysql数据库+系统+lw文档+部署
    电子科技大学《数据库原理及应用》(持续更新)
    你绝对不知道的接口加密解决方案:Python的各种加密实现
    JavaScript中的作用域及作用域链(es6)
    手把手教会你|Sockets多用户-服务器数据库编程
    安全扫描项目
  • 原文地址:https://blog.csdn.net/guohuang/article/details/133393605