问题
I am new to Ruby and currently trying a few examples from the Ruby book I am using as a guide:
class Account
attr_accessor :balance
def initialize(balance)
@balance = balance
end
end
class Transaction
def initialize(account_a, account_b)
@account_a = account_a
@account_b = account_b
end
def debit(account,amount)
account.balance -= amount
end
def credit(account,amount)
account.balance += amount
end
def transfer(amount)
debit(@account_a, amount)
credit(@account_b, amount)
end
end
savings = Account.new(100)
checking = Account.new(200)
trans = Transaction.new(checking, savings)
trans.transfer(60)
puts savings.balance
puts checking.balance
This is a pretty straightforward example containing two classes within the same script file. I am confused about the type of the argument I am passing to the credit and debit methods. Coming from Java, I am still thinging in terms of types, so obviously the type of the account variable i am passing to,say the debit method, has to be of type Account.
Since ruby is dynamically typed and is not checking for type, how can I safely operate on the argument I am passing, and define the rest of the method by saying: account.balance -+ amount ?
I am trying to understand, what kind of safety is there if I pass to the debit method a reference to an object other than Account?
When the body of the method below is defined, it uses the given parameter account. Now, I guess I am repeating myself because I still can't grasp the idea... How can I take the argument account (which can be of any type, since no-one is checking) and build some logic by using the dot operator, ask for its instance variable, or call its other methods, and perform calculations on an object which might, or might-not be, the correct kind (or type)? Of course, implicitely, I want it to be of type Account.
def credit(account,amount)
account.balance += amount
end
Also, how would the same example work if I declared the two classes in different files?
Sincere apologies in advance for the newbie questions, I just find it hard to wrap my mind around the dynamic typing -- or better, no type checking. The book is either a bit vague on this, or I can't shake my tunnel-vision by thinking only in java.
Any practical explanations would be greatly appreciated.
回答1:
You don't have any type safety in Ruby. All that matters to Ruby is whether an object can respond to the messages it's receiving. You can pass in something completely nonsensical and Ruby won't do anything to stop you at that point. But if you pass in an object that doesn't respond to the messages you're sending (+ and - in this case), you'll get a NoMethodError when your code tries to send the invalid message.
In general, the solution to this is: Don't pass in the wrong type. It know it sounds weak, but that's pretty much what you need to do — ensure you're passing the right thing. Write tests for your programs to make sure you're doing what you mean to do. Ruby is very big on unit-testing. If you're really worried about an argument being the correct kind of thing, you can either explicitly check its class (raise 'WTF?' unless object.class == String
) or you can try to convert it to the right class (by defining a to_foo
-type method).
In return for this, you're largely freed from caring about types. As long as the object responds to the messages you send, it doesn't matter what you pass in. This makes things like mocks and proxies really easy.
回答2:
It's called "duck typing" - I suggest reading the linked Wikipedia article first, and then see if you still have any questions remaining after that.
回答3:
The object passed, its reference, can be considered unknown at first glance, but Ruby is strongly typed; in addition, it has features that use "duck typing" to its benefit. I'll show a few examples.
Lets use the example of financial accounts.
You can make use of a case statement to determine the type of an object:
class Account
attr :balance # add ", true" to make it writeable
def initialize(balance)
case balance
when String: @balance = balance.to_f
when Fixnum, Float: @balance = balance
else
raise TypeError, "Can't initialize an account with a balance of type #{obj.class}."
end
end
end
Another option, when you've one test to make is the is_a?
test of the object, like obj.is_a?(Fixnum)
will return true/false on whether or not it is an Integer.
You can use an assertion to enforce the type:
class Account
attr :balance # add ", true" to make it writeable
def initialize(balance)
assert_eql balance.class, Float
@balance = balance
end
end
I would suggest that for the Transaction class you use an if-statement in conjunction with raising an exception:
class Transaction
def initialize(account_a, account_b)
raise ParameterError, "Both parameters must be accounts!" unless account_a.is_a?(Account) && account_b.is_a?(Account)
@account_a = account_a
@account_b = account_b
end
def transfer(amount)
debit(@account_a, amount)
credit(@account_b, amount)
end
private
def debit(account,amount)
account.balance -= amount
end
def credit(account,amount)
account.balance += amount
end
end
回答4:
Actually, all of the modern languages are type-safe and memory-safe, just not always statically typed
Those are good questions, and it's really the core of the dynamic-vs-static typing paradigm.
As it happens, most of your worries aren't too serious:
Ruby is type-safe, at least by its own definition, and it's definitely memory-safe. It does check types on every operation. (Sort of, some operations like parameter passing have no type restrictions to check.)
It does have a lot of automatic conversions, but it's still type-safe.
It does have dynamic typing, but it's still type-safe.
It's true, there is no "wrong" type for a method parameter. The check will happen when you use that parameter. There is also no need to check to see what type you received ... Ruby will check for you when you use it. You just get your errors at runtime instead of compile time, but you were going to unit test anyway, right? :-) It is useful to look up "Duck typing", as the definition of compatible types is wider than usual in Ruby, but there is still a definition and it is checked.
For a very large program, conventional wisdom maintains that statically typed languages like Java and Scala are safer choices, because they can be refactored with confidence and you get the extra compile-time checks. The conventional wisdom is disputed by many people, however, as it involves reducing a complex set of trade-offs to a 1 bit conclusion.
来源:https://stackoverflow.com/questions/1475048/type-of-object-references-in-ruby