- Published on
Learning Design Patterns: Proxy Pattern - The Legal Representative
- Authors
- Name
- Dan Tech
- @dan_0xff
The Proxy Pattern offers a new perspective in software architecture design, which is Delegated processing. Instead of business logic being called directly whenever needed, we place an intermediary layer (Proxy) in between to represent important processing from the Caller's side.
This Proxy class will act as a Gatekeeper, deciding whether to allow the call to proceed, temporarily HOLD the command pending processing, or even perform the logic itself and return the result immediately, without accessing the original data source.
This decision depends entirely on the software's load-bearing design problem and the specific requirements for the response speed of each Module / Feature.
Don't mistakenly believe that only code in the Backend or in Multi-Services architectures needs a Proxy. The Proxy Pattern is a software mindset, we will need to use it whenever necessary, whether it's Web, Mobile, Backend, Data, etc., as long as it meets the specifications of the Software / feature being built.
Describing the Proxy Pattern with Kotlin
// 1. Define the Subject Interface
interface Database {
fun getData(id: Int): String?
fun saveData(id: Int, data: String): Boolean
}
// 2. The Real Subject: The Actual Database Implementation
class RealDatabase : Database {
init {
println("RealDatabase: Initializing database connection...")
// Simulate a costly initialization (e.g., connection setup)
Thread.sleep(1000)
println("RealDatabase: Database connection established.")
}
override fun getData(id: Int): String? {
println("RealDatabase: Reading data with ID: $id")
// Simulate database read operation
return "Data for ID $id from the database."
}
override fun saveData(id: Int, data: String): Boolean {
println("RealDatabase: Saving data: $data with ID: $id")
// Simulate database write operation
return true
}
}
// 3. The Proxy: Manages Access to the Real Database
class DatabaseProxy(private val userId: String) : Database {
private var realDatabase: RealDatabase? = null
private val cache = mutableMapOf()
private fun getRealDatabase(): RealDatabase {
if (realDatabase == null) {
realDatabase = RealDatabase() // Lazy initialization
}
return realDatabase!!
}
override fun getData(id: Int): String? {
if (cache.containsKey(id)) {
println("DatabaseProxy: Returning data for ID $id from cache.")
return cache[id]
}
if (!authorize(userId, "READ")) {
println("DatabaseProxy: User $userId is not authorized to read data.")
return null
}
val data = getRealDatabase().getData(id)
data?.let { cache[id] = it } // Cache the data
logAccess("READ", id)
return data
}
override fun saveData(id: Int, data: String): Boolean {
if (!authorize(userId, "WRITE")) {
println("DatabaseProxy: User $userId is not authorized to write data.")
return false
}
val success = getRealDatabase().saveData(id, data)
if (success) {
cache[id] = data // Update the cache
}
logAccess("WRITE", id)
return success
}
private fun authorize(userId: String, permission: String): Boolean {
// Simulate authorization logic (e.g., check user roles)
return userId != "guest" || permission != "WRITE" // Guests can't write
}
private fun logAccess(operation: String, id: Int) {
println("DatabaseProxy: $operation operation on data with ID $id at ${LocalDateTime.now()}")
// Simulate logging (e.g., write to a file)
}
}
// 4. The Client
fun main() {
val user1 = DatabaseProxy("admin")
val user2 = DatabaseProxy("guest")
println("\n--- User 1 (Admin) Actions ---")
user1.saveData(1, "Important Data")
println("Data read: ${user1.getData(1)}")
println("Data read again: ${user1.getData(1)}") // From cache
println("\n--- User 2 (Guest) Actions ---")
user2.saveData(2, "Attempted Guest Write") // Authorization failure
println("Data read: ${user2.getData(2)}")
}
Comparing Proxy Pattern with Adapter, Facade, or Decorator
This part is important. We developers should read and remember this to be able to bring it up during interviews.
Reading up to here is okay! @dantech wishes you success!