Public and Private Interfaces in ruby
2016-05-02 22:09
525 查看
Your latest client is a bank, and they’ve tasked you with requiring customers to enter their password in order to make withdrawals.
Currently, this is what they’ve got:
Let’s break that apart. You can paste that whole class into irb to follow along.
When a customer is initialized, it receives a specified amount of funds and a password is set.
Thanks to the
And the
Checking on the funds again confirms this.
These methods,
An API is, according to Wikipedia, “a set of routines, protocols, and tools for building software applications”.
Well, that’s vague.
“API” is a popular term in recent years, but many people use it without quite understanding what it means. Think of methods like
There isn’t currently a way to access the
It could be said that the customer’s password can’t be accessed by the customer’s public API.
In this situation, that’s a good thing! You don’t want information like a password to be publicly available to other objects.
Let’s implement a method called
If the password entered matches the customer’s password, go ahead and remove the funds. Otherwise, nothing happens.
Play around with this in irb to see it in action.
✨Hooray. Calling
while using the incorrect password does nothing.
There’s one issue here, can you spot it?
Malicious users can still withdraw funds directly using the
With this current setup, both
In reality,
This can be done by making it a private method.
Private methods can be invoked only from within a class. If
that would mean it can only be accessed by another method, like
Here, anything after the
Okay,
But now
you’ll see that it does exactly what you intended: it will not let you call the private method,
Using private methods allows you to reuse logic in your code without having to worry about exposing it to other classes.
As a guiding principle, it’s not a bad idea to assume a method should be private until you’ve been given a good reason to make it a part of an object’s public API.
The
While this would technically work just fine, using the
There’s a little workaround using the
What does this mean for you? Well, if security is actually a primary concern for you, creating a private method alone isn’t enough of a solution.
But the benefits of tucking away private methods remain. Indicating that a method is private lets other developers know that a method isn’t intended to be part of an object’s public API, and discourages them from using it.
Currently, this is what they’ve got:
class Customer attr_reader :funds def initialize(funds, password) @funds = funds @password = password end def remove_funds(amount) @funds -= amount end end
Let’s break that apart. You can paste that whole class into irb to follow along.
When a customer is initialized, it receives a specified amount of funds and a password is set.
diego = Customer.new(500, "udacious") # => #<Customer:0x007fcdb48ca5a8 @funds=500 @password="udacious">
Thanks to the
attr_reader, you can see the value of his current funds.
diego.funds # => 500
And the
remove_fundsmethod allows funds to be removed from the customer’s account.
Checking on the funds again confirms this.
diego.remove_funds(50) # => 450 diego.funds # => 450
These methods,
fundsand
remove_funds, are part of the
Customerclass’ API, or application programming interface.
An API is, according to Wikipedia, “a set of routines, protocols, and tools for building software applications”.
Well, that’s vague.
“API” is a popular term in recent years, but many people use it without quite understanding what it means. Think of methods like
remove_fundsas your way of interfacing with the
Customerclass. These methods are the keys to accessing information about a particular customer.
There isn’t currently a way to access the
@passwordinstance variable.
It could be said that the customer’s password can’t be accessed by the customer’s public API.
In this situation, that’s a good thing! You don’t want information like a password to be publicly available to other objects.
Let’s implement a method called
withdraw_securely, which takes two arguments,
amountand
password.
If the password entered matches the customer’s password, go ahead and remove the funds. Otherwise, nothing happens.
class Customer attr_reader :funds def initialize(funds, password) @password = password @funds = funds end def remove_funds(amount) @funds -= amount end def withdraw_securely(amount, password) if password == @password remove_funds(amount) end end end
Play around with this in irb to see it in action.
diego.withdraw_securely(50, "udacious") # => 400 diego.withdraw_securely(100, "wrong password") # => nil diego.funds # => 400
✨Hooray. Calling
withdraw_securelyusing the correct password decreases the total funds by calling
remove_funds,
while using the incorrect password does nothing.
There’s one issue here, can you spot it?
diego.remove_funds(75) # => 325 diego.funds # => 325
Malicious users can still withdraw funds directly using the
remove_fundsmethod! 😱
With this current setup, both
remove_fundsand
withdraw_securelyare part of a customer’s public API.
In reality,
remove_fundsneeds to somehow be tucked away so that it isn’t accessible via the public API.
This can be done by making it a private method.
Private methods can be invoked only from within a class. If
remove_fundswere a private method,
that would mean it can only be accessed by another method, like
withdraw_securely. To do this, you can use the
privatekeyword.
class Customer attr_reader :funds public def initialize(funds, password) @password = password @funds = funds end def withdraw_securely(amount, password) if password == @password remove_funds(amount) end end private def remove_funds(amount) @funds -= amount end end
Here, anything after the
privatekeyword will be treated as a private method. Test it out in irb and see it in action!
diego.withdraw_securely(75, "udacious") # => 250
Okay,
withdraw_securelystill works as expected...
diego.remove_funds(100) NoMethodError: private method 'remove_funds' called for #<Customer:0x007fcdb496ae40 @funds=400, @password="udacious">
But now
remove_fundsthrows an error! A
NoMethodErrorin particular. If you read through this error,
you’ll see that it does exactly what you intended: it will not let you call the private method,
remove_funds.
Using private methods allows you to reuse logic in your code without having to worry about exposing it to other classes.
As a guiding principle, it’s not a bad idea to assume a method should be private until you’ve been given a good reason to make it a part of an object’s public API.
Going Public
Technically, there’s also apublickeyword. It works just like the
privatekeyword, and it’s used to indicate that methods should be publicly available.
The
publicand
privatekeywords (there’s also
protected, but we’ll get to that later) can only be used once inside each class, and methods are by default public.
class Customer attr_reader :funds public # public methods... private # private methods... end
While this would technically work just fine, using the
publickeyword tends to be redundant. You’ll hardly ever see it in code anywhere.
A disclaimer about Private Methods
Guilty confession: just because a method is private doesn’t mean it can’t ever be called.diego.send(:remove_funds, 65) # => 185
There’s a little workaround using the
sendmethod, which sends the method of the name you specify (in this case,
:remove_funds) to be called by the object.
What does this mean for you? Well, if security is actually a primary concern for you, creating a private method alone isn’t enough of a solution.
But the benefits of tucking away private methods remain. Indicating that a method is private lets other developers know that a method isn’t intended to be part of an object’s public API, and discourages them from using it.
相关文章推荐
- Ruby Cucumber环境
- Ruby基础教程(四)——运算符、异常、块
- Ruby基础教程(三)——类和模块
- Instance Variables in ruby
- Instance Variables in ruby
- 《Ruby基础教程》学习笔记
- Ruby on Rails: 使用devise+cancan+rolify建立完整的权限管理系
- Ruby on Rails: 使用devise+cancan+rolify建立完整的权限管理系
- 为什么说每位程序开发者应该学着写Ruby/Python
- Ruby入门之一(Ruby简介)
- ruby SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B:
- To build the ruby runtime environment on Mac
- 10 个惊艳的 Ruby 单行代码
- 反思Spring:由Ruby on Rails想到的
- ELK—>logstash—>ruby·plugin—>实现精彩的功能(term模板抽取)
- 手动安装rubygems
- Support Vector Machines (SVM) in Ruby
- SVD Recommendation System in Ruby
- ruby 除法运算
- [转]CocoaPods的安装使用 及Ruby环境的配置