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 useful for implementing lazy initialization, access control, logging, and more.

Real-World Example:

Imagine you’re a celebrity who can’t attend every event personally. You hire a representative (proxy) to attend on your behalf, manage interactions, and handle requests. The representative controls access to you, ensuring only appropriate interactions occur.

The Proxy pattern allows you to provide a substitute or placeholder for another object to control access to it.

Example:
Let’s implement a scenario where we have a Database interface, a RealDatabase class that performs actual database operations, and a DatabaseProxy that controls access to the RealDatabase.

// Subject
trait Database {
  def query(sql: String): String
}
 
// RealSubject
class RealDatabase extends Database {
  override def query(sql: String): String = {
    // Simulate a time-consuming database operation
    Thread.sleep(1000)
    s"Result for query: '$sql'"
  }
}
 
// Proxy
class DatabaseProxy extends Database {
  private lazy val realDatabase = new RealDatabase
 
  override def query(sql: String): String = {
    println(s"Logging: Executing query '$sql'")
    realDatabase.query(sql)
  }
}
 
// Usage
object ProxyPatternExample extends App {
  val db: Database = new DatabaseProxy
  println(db.query("SELECT * FROM users"))
}

Output:

Logging: Executing query 'SELECT * FROM users'
Result for query: 'SELECT * FROM users'

When to Use:

  • When you need to control access to an object.
  • When you want to add additional functionality to an object without changing its interface.
  • When creating an object is resource-intensive, and you want to delay its creation until it’s needed.

References: