What convention should I use for instance variables?
Is there any instance variable convention used in Rub开发者_如何学Goy code? Namely, I noticed that, in the examples section, instance variable are initialized using the '@' and then the code uses a variable name without the '@':
class BookInStock
attr_reader: isbn
attr_accessor: price
def initialize (isbn, price)
@isbn = isbn
@price = Float (price)
end
def price_in_cents
Integer (price * 100 + 0.5)
end
end
And there are examples where the code, the instance variable is used all the time with the prefix '@'
class BookStock
def set_price (price)
@price = price
end
def get_price
@price
end
end
What is the difference these records? When should I use '@' only in the initialization of an object and when in all the class methods?
You can only use the instance variable name without @
if you have defined an attribute reader for this variable (with attr_reader
or attr_accessor
). This is a lightweight helper to create a method that returns the instance variable's value.
The difference is a question between using the public interface of your class or accessing private data. It is recommended to use the public interface in subclasses or included modules, for example. This ensures encapsulation. So you should not access instance variables from within subclasses or modules.
In the class itself, ask yourself this question: if the implementation of the attribute changes, should my usage the attribute change as well? If the answer is yes, use the instance variable directly; if it's no, use the public attribute.
An example. Suppose we have a Person
class that represents a person and should contain that person's name. First there is this version, with only a simple name
attribute:
class Person
attr_reader :name
def initialize(name)
@name = name
end
def blank?
!@name
end
def to_s
name
end
end
Note how we use the instance variable for the blank?
method, but the public attribute for the to_s
method. This is intentional!
Let's say that in a refactoring step we decide that the Person
class should keep track of a first_name
and last_name
seperately.
class Person
def initialize(first_name, last_name)
@first_name, @last_name = first_name, last_name
end
def name
"#{@first_name} #{@last_name}"
end
def blank?
!@first_name and !@last_name
end
def to_s
name
end
end
We now change the name
attribute to a regular method, which composes the name from a person's first and last name. We can now see that it is possible to keep the same implementation for to_s
. However, blank?
needs to be changed because it was dependent on the underlying data. This is the reason why to_s
used the attribute, but blank?
used the instance variable.
Of course, the distinction is not always easy to make. When in doubt, choose the public API over accessing private data.
In your first example, @price
is an instance variable.
In the method definition for price_in_cents
, price
is actually a method call to price()
. Because of Ruby's syntactic sugar, ()
is omitted.
It looks like there's no explicit definition for price()
, but attr_accessor :price
defined both price()
and price=(value)
a.k.a. getter and setter. You can try to comment out the attr_accessor :price
line, you will get an "undefined local variable or method" exception when the BookInStock
's instance calls price_in_cents
.
An instance variable is always have an @
prefix. If no accessor methods defined, you can not use that name without @
prefix.
Using name = value
is an error, because that creates a local variable named name
. You must use self.name = value
.
As for convention, you can only get away with using @name
if you can guarantee that the accessors will always be lightweight attr_accessors. In all other cases, using @name
over self.name
will violate encapsulation and give yourself a headache. You gave the exact reason in your question — if there is extra logic in the getter/setter, you must duplicate it if you access the instance variable directly.
精彩评论