Design Patterns in Ruby [Digest 8] Proxy

 

When we meet these requirements:

1) Controlling access to an object

2) providing a location-independent way of getting at the object

3) delaying its creation

all three actually have a common solution: the Proxy pattern.

 

Code First:

 

 

class BankAccount
	attr_reader :balance
	
	def initialize(starting_balance=0)
		@balance = starting_balance
	end
	
	def deposit(amount)
		@balance += amount
	end
	
	def withdraw(amount)
		@balance -= amount
	end
end

 

 

 

class BankAccountProxy
	def initialize(real_object)
		@real_object = real_object
	end
	
	def balance
		@real_object.balance
	end
	
	def deposit(amount)
		@real_object.deposit(amount)
	end
	
	def withdraw(amount)
		@real_object.withdraw(amount)
	end
end

 

the proxy object can treated as couterfeit object:

 

 

account = BankAccount.new(100)
account.deposit(50)
account.withdraw(10)

proxy = BankAccountProxy.new(account)
proxy.deposit(50)
proxy.withdraw(10)

The BankAccountProxy doesn't know much about the finance, he just send request to target object.

 

If we consider the access security issue, the folowing code will answer:

 

require 'etc'
class AccountProtectionProxy
	def initialize(real_account, owner_name)
		@subject = real_account
		@owner_name = owner_name
	end

	def deposit(amount)
		check_access
		return @subject.deposit(amount)
	end

	def withdraw(amount)
		check_access
		return @subject.withdraw(amount)
	end

	def balance
		check_access
		return @subject.balance
	end
	
	def check_access
		if Etc.getlogin != @owner_name
			raise "Illegal access: #{Etc.getlogin} cannot access account."
		end
	end
end

 

 

The advantage of using a proxy for protection is that it gives us a nice separation of concerns: The proxy worries about who is or is not allowed to do what.

 

Protection proxies have another advantage over the naive “implement all of the security and functionality in one class” approach.

 

If we consider the remote proxy:

we can use the Ruby soap client for example:

 

require 'soap/wsdlDriver'
wsdl_url = 'http://www.webservicex.net/WeatherForecast.asmx?WSDL'
proxy = SOAP::WSDLDriverFactory.new( wsdl_url ).create_rpc_driver
weather_info = proxy.GetWeatherByZipCode('ZipCode'=>'19128')

 Once the proxy object is set up, the client code no longer has to worry about the fact that the service actually lives at www.webservicex.net. Instead, it simply calls GetWeatherByZipCode and leaves all of the network details to the proxy.

 

If we consider the lazy creating issue:

 

class VirtualAccountProxy
	def initialize(starting_balance=0)
		@starting_balance=starting_balance
	end
	
	def deposit(amount)
		s = subject
		return s.deposit(amount)
	end
	
	def withdraw(amount)
		s = subject
		return s.withdraw(amount)
	end
	
	def balance
		s = subject
		return s.balance
	end
	
	def subject
		@subject || (@subject = BankAccount.new(@starting_balance))
	end
end

 

The lazy creating is really simple, we can create by proc passing from the contrusctor:

 

class VirtualAccountProxy
	def initialize(&creation_block)
		@creation_block = creation_block
	end
	# Other methods omitted ...
	def subject
		@subject || (@subject = @creation_block.call)
	end
end

account = VirtualAccountProxy.new { BankAccount.new(10) }

 

The delegate by method_missing:

If you can sth.some_method if some_method is missing in sth instance, then it will climb up to its super  classes to find it, if still not found, it will call the sth.method_missing, if sth.method_missing not found, it will call up to super classes till the Object class(Object has the method) it will raise a NoMethodError exception.

 

So we can eliminate the direct proxy method

 

class AccountProtectionProxy
	def initialize(real_account, owner_name)
		@subject = real_account
		@owner_name = owner_name
	end
	
	def method_missing(name, *args)
		check_access
		@subject.send( name, *args )
	end
	
	def check_access
		if Etc.getlogin != @owner_name
			raise "Illegal access: #{Etc.getlogin} cannot access account."
		end
	end
end

 

 

class VirtualProxy
	def initialize(&creation_block)
		@creation_block = creation_block
	end

	def method_missing(name, *args)
		s = subject
		s.send( name, *args)
	end

	def subject
		@subject = @creation_block.call unless @subject
		@subject
	end
end

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值