Explore the Proxy Design Pattern in Go, its types, implementation, and practical examples. Learn how to control access and enhance functionality using proxies.
The Proxy design pattern is a structural pattern that provides a surrogate or placeholder for another object to control access to it. This pattern is particularly useful when you need to add a level of indirection to access an object, allowing you to add functionality such as lazy initialization, access control, or logging without modifying the original object’s code.
Define an Interface: Create an interface that both the real subject and the proxy will implement. This ensures that the proxy can be used interchangeably with the real subject.
Implement the Real Subject: Develop the real subject class that performs the actual work.
Create the Proxy Struct: Implement the proxy struct that holds a reference to the real subject. The proxy will implement the same interface as the real subject.
Control Access: The proxy will control access to the real subject and may add additional behavior such as logging, caching, or access control.
Remote Proxy: Provides a local representative for an object that exists in a different address space. This is commonly used in distributed systems to manage network communication.
Virtual Proxy: Delays the creation and initialization of expensive objects until they are actually needed. This can significantly improve performance and resource utilization.
Protection Proxy: Controls access to the original object based on access rights. This is useful for implementing security policies.
Let’s consider an example where we use a virtual proxy to manage access to a large image object. The proxy will handle the lazy loading of the image, meaning the image will only be loaded when it is actually needed.
1package main
2
3import (
4 "fmt"
5 "sync"
6)
7
8// Image is the interface that both RealImage and ProxyImage will implement.
9type Image interface {
10 Display()
11}
12
13// RealImage is the actual object that is expensive to create.
14type RealImage struct {
15 filename string
16}
17
18// Display loads and displays the image.
19func (img *RealImage) Display() {
20 fmt.Println("Displaying", img.filename)
21}
22
23// loadImage simulates loading an image from disk.
24func loadImage(filename string) *RealImage {
25 fmt.Println("Loading image from disk:", filename)
26 return &RealImage{filename: filename}
27}
28
29// ProxyImage is the proxy that controls access to the RealImage.
30type ProxyImage struct {
31 filename string
32 realImage *RealImage
33 once sync.Once
34}
35
36// Display ensures the real image is loaded before displaying it.
37func (proxy *ProxyImage) Display() {
38 proxy.once.Do(func() {
39 proxy.realImage = loadImage(proxy.filename)
40 })
41 proxy.realImage.Display()
42}
43
44func main() {
45 image := &ProxyImage{filename: "large_image.jpg"}
46
47 // The image is loaded from disk only when Display is called for the first time.
48 image.Display()
49
50 // Subsequent calls to Display do not load the image again.
51 image.Display()
52}
Image interface defines a Display method that both RealImage and ProxyImage implement.RealImage struct represents the actual image object that is expensive to load.ProxyImage struct acts as a proxy to the RealImage. It uses a sync.Once to ensure that the image is loaded only once, demonstrating lazy loading.Display method in ProxyImage uses sync.Once to load the image only when it is first accessed, optimizing resource usage.The Proxy design pattern is a powerful tool for controlling access to objects, adding functionality, and optimizing resource usage. By understanding its types and implementation strategies, you can effectively leverage proxies in your Go applications to enhance performance and maintainability.