Thursday, May 18, 2023

Proxy Design Pattern with Kotlin and comparative analysis with Java

 Proxy

This is one misbehaving design pattern. Much like Decorator, it extends object functionality. But, unlike Decorator, which always does at it's told, having a Proxy may mean that when asked, the object will do something totally different instead.

A short detour into the RMI world While discussing Proxy, a lot of sources, mostly related to Java, diverge into discussing another concept, RMI. RMI in the JVM world stands for Remote Method Invocation, which is a sort of Remote Procedure Call (RPC). What that means is that you're able to call some code that doesn't exist on your local machine, but sits on some remote machine. Although a very clever solution, it's very JVM specific, and has become less popular in the era of microservices, where each piece of code may be written in a totally different programming language.

A replacement When we discussed Creational Patterns, we already discussed the idea of expensive objects. For example, an object that accesses network resources, or takes a lot of time to create. We at Funny Cat App (name invented by the canary Michael; remember him from the Flyweight pattern?) provide our users with funny cat images on a daily basis. On our homepage and mobile application, each user sees a lot of thumbnails of funny cats. When he clicks or touches any of those images, it expands to its full-screen glory. But fetching cat images over the network is very expensive, and it consumes a lot of memory, especially if those are images of cats that tend to indulge themselves in a second dessert after dinner. So, what we would like to do is to have a smart object, something that manages itself. When the first user access this image, it will be fetched over the network. No way of avoiding that.

But when it's being accessed for the second time, by this or some other user, we would like to avoid going over the network again, and instead return the result that was cached. That's the misbehaving part, as we described. Instead of the expected behavior of going over the network each time, we're being a bit lazy, and returning the result that we already prepared. It's a bit like going into a cheap diner, ordering a hamburger, and getting it after only two minutes, but cold. Well, that's because someone else hated onions and returned it to the kitchen a while ago. True story. That sounds like a lot of logic. But, as you've probably guessed, especially after meeting the Decorator design pattern, Kotlin can perform miracles by reducing the amount of boilerplate code you need to write to achieve your goals: 

data class CatImage(private val thumbnailUrl: String, private val url: String) {

    val image: java.io.File by lazy {

        // Actual fetch goes here

    }

As you may notice, we use the by keyword to delegate initialization of this field to a standard function called lazy.

The first call to image will execute a block of our code and save its results into the image property. Sometimes, the Proxy design pattern is divided into three sub-patterns: Virtual proxy: Lazily caches the result Remote proxy: Issues a call to the remote resource Protection or access control proxy: denies access to unauthorized parties Depending on your views, you can regard our example as either a virtual proxy or a combination of virtual and remote proxies.

Lazy delegation You may wonder what happens if two threads try to initialize the image at the same time. By default, the lazy() function is synchronized. Only one thread will win, and others will wait until the image is ready. If you don't mind two threads executing the lazy block (it's not that expensive, for example), you can use by lazy(LazyThreadSafetyMode.PUBLICATION). If performance is absolutely critical to you, and you're absolutely sure that two threads won't ever execute the same block simultaneously, you can use LazyThreadSafetyMode.NONE.

Code Example :

import java.io.BufferedOutputStream
import java.io.File

fun main(args: Array<String>) {

val catImage34 = CatImage("https://funny.cats/thumb-34.jpg",
     "https://upload.wikimedia.org/wikipedia/commons/
      thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg")
    println(catImage34.image.toPath())
catImage34.image
catImage34.image
}

data class CatImage(private val thumbnailUrl: String, private val url: String) {
val image: java.io.File by lazy {
println("Fetching image over network")
val f = File.createTempFile("cat", ".jpg")
java.net.URI.create(url).toURL().openStream().use {
it.copyTo(BufferedOutputStream(f.outputStream()))
}.also { println("Done fetching") }
f
}
}

Ouput :

Fetching image over network

Done fetching

C:\Users\ABHINA~1\AppData\Local\Temp\cat13192362287454459013.jpg





1 comment:

Anonymous said...

Awesome explanation with a very nice example.