Skip to content

Instantly share code, notes, and snippets.

@david-pm
Last active December 29, 2018 03:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save david-pm/76c3e84ac390d091c1cc65653e06274e to your computer and use it in GitHub Desktop.
Save david-pm/76c3e84ac390d091c1cc65653e06274e to your computer and use it in GitHub Desktop.
Protection Proxy Pattern example that respects Interface Segregation
class BankAccount
attr_reader :balance
def initialize(starting_balance)
@balance = starting_balance
end
# if this were #irl we'd need to worry about race conditions here
def deposit(amount)
@balance += amount
end
def withdraw(amount)
@balance -= amount
end
end
# The Etc module provides access to information typically stored in files
# in the /etc directory on Unix systems.
# require 'etc'
# one approach taken from the excellent book Design Patterns in Ruby by Russ Olsen# class AccountProtectionProxy
# this is great since we dont need to write proxy methods by hand, and this class will never need to change!
#
# class AccountProtectionProxy
# def initialize(real_account, owner_name)
# @real_account = real_account
# @owner_name = owner_name
# end
# def method_missing(name, *args)
# access_control!
# @real_account.send(name, *args)
# end
# private def access_control!
# raise("Access denied! #{Etc.getlogin} is unknown") unless Etc.getlogin == @owner_name
# end
# end
# the downside, in my opinion, is that this AccountProtectionProxy class will blindly
# proxy any object you give it
#
# the essence of the Interface Segregation Principle is that a client should be provided with an
# interface limited only to the methods needed in the context of the client's interaction
#
# so, let's be more explicit about what we want to expose but still obviate the need to
# write out each proxied method manually. we use the Forwardable module to do so:
require 'etc'
require 'forwardable'
class AccountProtectionProxy
extend Forwardable
def_delegators :@real_account, :balance, :deposit, :withdraw
def initialize(real_account, owner_name)
@real_account = real_account
@owner_name = owner_name
end
private def access_control!
raise("Access denied! #{Etc.getlogin} is unknown") unless Etc.getlogin == @owner_name
end
end
ba = BankAccount.new(100)
proxy = AccountProtectionProxy.new(ba, 'bob')
proxy.deposit 20
puts proxy.balance
proxy.withdraw 120
puts proxy.balance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment