Proxy Design Pattern in Kotlin

1. Definition

The Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it. It can add a level of indirection to the actual object's operations or perform some operations before or after forwarding the request to the real object.

2. Problem Statement

Imagine you have a resource-intensive object, and you want to delay its creation until it's truly necessary. Or you might want to add additional functionality (like access control, logging, etc.) when accessing that object.

3. Solution

The Proxy pattern suggests creating a new proxy class with the same interface as the original object. This proxy class can control the lifecycle of the original object, and add additional logic before or after forwarding calls to the original object.

4. Real-World Use Cases

1. Lazy initialization for resource-intensive objects.

2. Access control for sensitive operations.

3. Logging and monitoring accesses to objects.

4. Remote proxies which represent objects located in different address spaces.

5. Implementation Steps

1. Define an interface that both the RealObject and Proxy will implement.

2. Create the RealObject class that implements the interface.

3. Create the Proxy class that also implements the interface, holds a reference to the RealObject, and controls access to it.

6. Implementation in Kotlin

// Step 1: Define the interface
interface Image {
    fun display(): Unit
}

// Step 2: Create the RealObject class
class RealImage(private val filename: String) : Image {
    init {
        loadFromDisk()
    }

    private fun loadFromDisk() {
        println("Loading Image: $filename")
    }

    override fun display() {
        println("Displaying Image: $filename")
    }
}

// Step 3: Create the Proxy class
class ProxyImage(private val filename: String) : Image {
    private var realImage: RealImage? = null

    override fun display() {
        if (realImage == null) {
            realImage = RealImage(filename)
        }
        realImage?.display()
    }
}

fun main() {
    val image1: Image = ProxyImage("photo1.jpg")
    val image2: Image = ProxyImage("photo2.jpg")

    image1.display()  // This will load and then display the image
    image1.display()  // This will only display the image
    image2.display()  // This will load and then display the image
}

Output:

Loading Image: photo1.jpg
Displaying Image: photo1.jpg
Displaying Image: photo1.jpg
Loading Image: photo2.jpg
Displaying Image: photo2.jpg

Explanation:

1. We defined an Image interface with a display method.

2. RealImage is our real object that implements the Image interface. It loads the image from disk during initialization.

3. ProxyImage is our proxy class that also implements the Image interface. It has a reference to RealImage. When display is called, it checks if the real image is loaded; if not, it loads it. Then it delegates the display call to the real image.

4. In the main function, we demonstrated that using the proxy, the real image is loaded only once and only when it's needed.

7. When to use?

The Proxy Design Pattern is useful when:

1. You need a more versatile or sophisticated reference to an object than a simple pointer.

2. Objects need to be created on demand or loaded lazily.

3. Access control, logging, or other additional functionality should be added to the object access.


Comments