fast_forwardProxy
The Proxy design pattern is a structural pattern that involves the use of a surrogate or placeholder object to control access to another object. It acts as an intermediary, allowing or restricting access to the real object.
In today’s example, we will build an application that will allow different users to manage one bank account. First, let’s build our class, which will create our account with the initial balance and allow us to:
check account balance
deposit money
withdraw money
So let’s create Account class.
class Account
def initialize
@balance = 20_000
end
def balance(user:)
@balance
end
def deposit(amount:, user:)
@balance += amount
end
def withdraw(amount:, user:)
@balance -= amount
end
end
Let’s also create a User class that will store only a user name.
class User
attr_reader :name
def initialize(name)
@name = name
end
end
With the code prepared in this way, each User will be able to easily withdraw and deposit money into the account.
account = Account.new
john = User.new('John Doe')
jane = User.new('Jane Doe')
account.balance(user: john) # => 20000
account.deposit(amount: 1350, user: john) # => 21350
account.withdraw(amount: 2000, user: jane) # => 19350
To use the Proxy pattern, we need to create a new class that will have the same interface as the base class, but to make it working we will pass our base class to its constructor.
class ProxyAccount
attr_reader :account
def initialize(account)
@account = account
end
def balance(user:)
account.balance(user:)
end
def deposit(amount:, user:)
account.deposit(amount:, user:)
end
def withdraw(amount:, user:)
account.withdraw(amount:, user:)
end
end
With the class prepared in this way, we can add a new method that will add some information and place it before each operation.
class ProxyAccount
attr_reader :account
def initialize(account)
@account = account
end
def balance(user:)
log(user, "Check account balance")
account.balance(user:)
end
def deposit(amount:, user:)
log(user, "send #{amount}$ to account")
account.deposit(amount:, user:)
end
def withdraw(amount:, user:)
log(user, "Take #{amount}$ from account")
account.withdraw(amount:, user:)
end
private
# Display time of the action and who did it
def log(user, text)
puts "#{Time.now} | #{user.name} | #{text}"
end
end
Thanks to the fact that our proxy class has the same interface as the base class, we can use it interchangeably and receive logs before starting each operation.
proxy_account = ProxyAccount.new(Account.new)
proxy_account.balance(user: john)
# 2024-01-25 18:55:06 +0100 | John Doe | Check account balance
# => 20000
proxy_account.withdraw(amount: 2000, user: jane)
# 2024-01-25 18:55:43 +0100 | Jane Doe | Take 2000$ from account
# => 18000
Another very popular adoption of this pattern is access control. Therefore, let’s add a method that will raise an error when the withdrawal limit exceeds 5000 $.
class ProxyAccount
attr_reader :account
def initialize(account)
@account = account
end
def balance(user:)
log(user, "Check account balance")
account.balance(user:)
end
def deposit(amount:, user:)
log(user, "send #{amount}$ to account")
account.deposit(amount:, user:)
end
def withdraw(amount:, user:)
log(user, "Take #{amount}$ from account")
check_limit(amount)
account.withdraw(amount:, user:)
end
private
# Display time of the action and who did it
def log(user, text)
puts "#{Time.now} | #{user.name} | #{text}"
end
def check_limit(amount)
return if amount < 5000
raise("Blocked! Transaction value too high.")
end
end
Now let’s use the new implementation to check whether it works properly.
proxy_account = ProxyAccount.new(Account.new)
proxy_account.withdraw(amount: 7500, user: jane) # =>
# Blocked! Transaction value too high. (RuntimeError)
In summary, the Proxy Pattern is useful in situations where you need to add an additional layer of control, functionality, or optimization to the access of an object, without directly modifying the object’s code.