Proxy Design Pattern Using Python: A Banking Example

Manishankar Jaiswal
5 min readSep 16, 2024

--

Design patterns are essential for building robust, maintainable, and scalable applications. Among them, the Proxy Design Pattern plays a key role in controlling access to objects and adding an extra layer of functionality without modifying the original object’s code. In this blog, we will explore the Proxy Design Pattern using Python, but instead of using the typical video player example, we’ll use a banking system scenario to make the concept more relatable.

Proxy Design Pattern
Proxy Design Pattern

What is the Proxy Design Pattern?

The Proxy Design Pattern is a structural pattern that provides a surrogate or placeholder for another object. The proxy acts as an intermediary between the client and the real object, often controlling access to the real object and adding functionality such as logging, caching, or access control.

The Proxy pattern allows you to:

  • Control access to an object.
  • Lazy initialization (delaying the object creation until needed).
  • Add additional functionality such as logging, caching, or security without changing the object’s code.
  • Manage resource-intensive objects by creating them only when necessary.

Importance of the Proxy Design Pattern

In real-world applications, some objects are expensive to create, maintain, or use directly. For example, consider a banking system where sensitive data is being processed. We cannot afford to access the bank database directly every time a client needs to view or modify data. Instead, a proxy could manage access to the database, adding a layer of security and logging user activities.

Proxy Pattern Structure

The Proxy Design Pattern typically consists of the following components:

  • Subject: Defines the common interface for RealSubject and Proxy.
  • RealSubject: The actual object that the proxy represents.
  • Proxy: Controls access to the RealSubject and may add additional functionality (e.g., access control, logging).

A Real-World Example: Proxy in a Banking System

Imagine you are building a banking system where users can check their account balance. The system fetches data from a real database, but directly accessing the database every time can be inefficient and insecure. We’ll use a proxy that adds security checks (e.g., ensuring the user is authenticated) and logging (e.g., tracking when the balance was viewed).

Problem

In a banking system, accessing account data should be secure, and the operations should be logged. However, the database is a critical resource, so we need to avoid overloading it with frequent queries. A proxy can provide access control and ensure that only authenticated users access their account while logging every access attempt.

Step-by-Step Implementation in Python

Let’s implement this using Python.

from abc import ABC, abstractmethod

# Step 1: Create an interface (Subject)
class BankAccount(ABC):
@abstractmethod
def get_balance(self):
pass

# Step 2: Create the RealSubject class (Real Bank Account)
class RealBankAccount(BankAccount):
def __init__(self, balance):
self._balance = balance

def get_balance(self):
# Simulating a real-time database call
return f"Account balance is: ${self._balance}"

# Step 3: Create the Proxy class (Proxy Bank Account)
class ProxyBankAccount(BankAccount):
def __init__(self, real_account, user):
self.real_account = real_account
self.user = user

def authenticate(self):
# Simple check for demonstration (in reality, you'd check username/password)
return self.user == "authorized_user"

def get_balance(self):
# Adding access control (authentication check)
if not self.authenticate():
return "Access Denied: Unauthorized user."

# Adding logging functionality
print(f"Logging: User {self.user} accessed the account.")

# Forward the request to the real object
return self.real_account.get_balance()

# Step 4: Use the Proxy class to control access
def main():
# Creating the real bank account
real_account = RealBankAccount(5000)

# Creating the proxy with an authorized user
proxy_account = ProxyBankAccount(real_account, "authorized_user")

# Accessing the balance through the proxy
print(proxy_account.get_balance()) # This should succeed

# Trying with an unauthorized user
proxy_account_unauthorized = ProxyBankAccount(real_account, "unauthorized_user")
print(proxy_account_unauthorized.get_balance()) # This should fail

if __name__ == "__main__":
main()

Breakdown of the Code

  • BankAccount (Subject Interface): This is the common interface that both the real bank account (RealBankAccount) and the proxy (ProxyBankAccount) implement. It defines the method get_balance().
  • RealBankAccount (RealSubject): This class contains the real logic to get the account balance from a database (simulated by a balance variable).
  • ProxyBankAccount (Proxy): This is where the proxy pattern comes into play. The proxy controls access to the real bank account by adding authentication and logging before allowing access to the RealBankAccount.
  • Authentication and Logging: Before calling the get_balance() method of the real bank account, the proxy checks if the user is authorized and logs the access attempt.

Why Use Proxy in Banking Systems?

  • Security: In banking systems, ensuring that only authenticated users access account data is critical. A proxy can handle the authentication process before accessing the main banking system.
  • Logging: Proxies can automatically log sensitive actions, like checking balances or transactions, ensuring compliance and auditing.
  • Lazy Initialization: In cases where accessing the database is resource-intensive, proxies can delay the actual database access until it’s absolutely necessary, improving system efficiency.

Features of the Proxy Design Pattern

  • Access Control: Proxies control who has access to the real object, adding a layer of security.
  • Lazy Initialization: The real object is only created or accessed when needed, saving system resources.
  • Logging and Monitoring: Proxies can add logging or monitoring to keep track of interactions with the real object.
  • Resource Management: Proxies help manage resource-intensive objects by controlling how and when they are accessed.

When to Use the Proxy Design Pattern?

  • Security: When you need to control who can access a resource.
  • Logging or Monitoring: When you want to track how an object is used.
  • Lazy Loading: When creating an object is expensive and you want to delay its creation until absolutely necessary.
  • Remote Access: When the real object resides on a remote server, a proxy can represent it locally.

Conclusion

The Proxy Design Pattern is a versatile and powerful tool, especially in scenarios where access control, security, or resource management is critical. In the banking system example, we saw how a proxy can ensure that only authenticated users access sensitive account data while logging every access attempt.

This pattern allows you to introduce additional functionality without modifying the real object’s code, keeping your design clean and maintainable. By implementing the proxy pattern, you enhance both security and performance in your applications.

In the next post, we’ll explore another design pattern that complements the proxy pattern: the Decorator Design Pattern, which adds dynamic functionality to objects without changing their structure.

--

--

No responses yet