您的位置:首页 > 编程语言 > Ruby

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:

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_funds
method 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,
funds
and
remove_funds
, are part of the
Customer
class’ 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_funds
as your way of interfacing with the
Customer
class. These methods are the keys to accessing information about a particular customer.

There isn’t currently a way to access the
@password
instance 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,
amount
and
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_securely
using 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_funds
method! 😱

With this current setup, both
remove_funds
and
withdraw_securely
are part of a customer’s public API.

In reality,
remove_funds
needs 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_funds
were a private method,

that would mean it can only be accessed by another method, like
withdraw_securely
. To do this, you can use the
private
keyword.

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
private
keyword 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_securely
still works as expected...

diego.remove_funds(100)
NoMethodError: private method 'remove_funds' called for
#<Customer:0x007fcdb496ae40 @funds=400, @password="udacious">


But now
remove_funds
throws an error! A
NoMethodError
in 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 a
public
keyword. It works just like the
private
keyword, and it’s used to indicate that methods should be publicly available.

The
public
and
private
keywords (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
public
keyword 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
send
method, 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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: