Proxy Design Pattern with Kotlin using Delegate

Introduction

Previously, in the blog Implementing Lazy Loading with Proxy Design Pattern, I discussed about Proxy Pattern, in particular Remote Proxy. Lazy loading benefit and Fast Compiling are demonstrated. Various considerations are needed to implement the pattern with C++.

Kotlin and C# provides a new concept and implementation: Delegate which helps creating more concise program.

In this blog, I discuss Delegate concept, Proxy pattern, re-cap the consideration needed to implement the pattern with C++. Finally I will provide implementation code with Kotlin.

Concepts

Delegation

A delegate is a type that represents a reference to a method(s). It allows user to call a function with defined signature via another entity.

  • For C#, "a delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance. Delegates are used to pass methods as arguments to other methods"
    • A delegate is similar to function pointer, but unlike C++, a delegate "encapsulates both an object instance and a method"
  • For Kotlin, a delegate properties is declared as a property inside a class. The property has the type of the remote class that we need to invoke methods.
    • Kotlin delegate properties have built-in implementation that support various features. They are Lazy delegate, Observable delegate, and Storing delegate.

Proxy Pattern

A proxy pattern refers to design that allows client to call functions of a service class via a proxy class. There are different types of proxies:

  • Remote Proxy: data could be stored to local storage by proxy class.
  • Virtual Proxy: provides lazy loading. Real service class is only initialized once, when needed by the proxy class.
  • Protection Proxy: access to the service class is restricted and controlled by the proxy class

C++ Virtual Proxy implementation

Detail implementation of Virtual Proxy pattern was described in this blog post. Here are some re-caps:

  • There is an interface that abstracts the functions to be called by clients
  • Service class implements the interface and the functions.
  • Proxy class implements the interface and the functions. The service class has either aggregation or composition relationship with the Service class.
    • Proxy class has a pointer to Service class. The pointer is initially null. This is required for safe initialization of pointer.
    • Proxy class initializes Service when the client invokes requested functions for the first time.
    • Proxy class checks if the pointer to Service class is null or not.
    • When the client requests service, the proxy invokes the requested function via Proxy class.
    • Proxy class can use function pointer to invoke Real class's function.
    • When Proxy class is deleted, it also deletes the instance of Service class, and assigns the member pointer to Service class to null. This is required for safe delete of pointer.
  • Client has to initialize the Proxy Class.

Kotlin Proxy implementation

Implement basic delegate to route request to service class.

Similar to C++, Kotlin implementation of Proxy Pattern requires an interface, a proxy class and a service class.

However, since Kotlin is designed to be concise, (short and clear), we don't need to implement complicating constructor, destructor, null pointer checking, pointer initialization etc. Instead, by keyword is sufficient.

  • Lazy property: the value is computed only on first access
  • lazy() is a function that takes a lambda and returns an instance of Lazy<T>, which can serve as a delegate for implementing a lazy property.
    • The first call to get() executes the lambda passed to lazy() and remembers the result.
    • Subsequent calls to get() simply return the remembered result [1]

Client has to create an instance of Real class and Proxy Class. The instance of Real class is passed to the Proxy Class.

// Define the interface
interface DataService {
    fun loadData()
}

// Implement the interface in the Data class
class Data : DataService {
    override fun loadData() {
        println("Loading data...")
        // Implementation logic goes here
    }
}

// Implement the DataProxy class with the delegate keyword
class DataProxy(private val delegate: DataService) : DataService by delegate

fun main() {
    val data = Data()
    val dataProxy = DataProxy(data)
    dataProxy.loadData()
}

Implement Lazy Delegate

The above program is modified so that the Data class is initialized only once, the first time when loadData function is called by client.

Diagram describing the program with Proxy Pattern

We can implement that by removing the constructor in the DataProxy class and instead define a lazy delegate property inside the DataProxy class

// Define the interface
interface DataService {
    fun loadData()
}

// Implement the interface in the Data class
class Data : DataService {
    override fun loadData() {
        println("Loading data...")
        // Implementation logic goes here
    }
}


// Remove DataProxy constructor that includes delegate
// class DataProxy(private val delegate: DataService) : DataService by delegate

// Implement the DataService interface in the DataProxy class using delegation with lazy initialization
class DataProxy : DataService {
    private val data: DataService by lazy { 
        println("Data is initialized")
        Data() 
    }

    override fun loadData() {
        data.loadData()
    }
}

fun main() {
    val dataProxy = DataProxy()

    println("Before calling loadData")

    dataProxy.loadData() // Creates an instance of Data and calls loadData

    println("After calling loadData")

    dataProxy.loadData() // Reuses the previously created instance of Data and calls loadData again
}

The output indicates that Data instance is only called once:

Before calling loadData
Data is initialized
Loading data...
After calling loadData
Loading data...

References

[1] Delegated properties | Kotlin Documentation (kotlinlang.org)

Leave a Reply

Your email address will not be published. Required fields are marked *