简体   繁体   中英

Ruby: Parameter vs variables

So I started learning ruby and I found out that we can have default values in a method, this looked similar to just having instance variables, Is there any importance or benefit this gives apart from reducing the lines of code?

//This

def order_food(price, size="large", quantity = 8)
    if quantity == 1
      extra = "salad"
    else
      extra = "Burgers"
    end
    puts "#{quantity} #{size} #{extra}, coming right up!"
  end

//To this

def order_food(price)
    size = "large"
    quantity = 8
    if quantity == 1
      extra = "salad"
    else
      extra = "Burgers"
    end
    puts "#{quantity} #{size} #{extra}, coming right up!"
  end

While thinking deep into this I realized one of the very great benefit this provides is flexibility and readability. So for instance, I could pass in parameters like

order_food(2, "small", 90)

This allows me to override the default values which is better than having to change a variables content while

order_food(9, "extraLarge")

gets the default quantity that I have set

It's not the same as an instance variable. An instance variable has scope for an instance of a class and is declared using the @ symbol.

For example:

class MyClass

  def initialise
    @my_instance_variable = 'hello world'
  end

  def method_one
    puts "I have access to the instance variable and it is #{@my_instance_variable}"
  end
end

What you have shown are both declarations of local variables for the scope of the method only, however one is defining a parameter to your method and the other isn't.

def order_food(price, size="large", quantity = 8)

is not the equivalent of:

def order_food(price)
    size = "large"
    quantity = 8

Although size and quantity are both variables and both have scope only for the order_food method, the first is declaring them as parameters the method can accept, so it can be called like so:

order_food(5, 'small', 2)

Whereas in the second example these cannot be set by the callee - they are fixed at 'large' and 8.

It is not necessary to declare method parameters with defaults but by doing so the callee doesn't need to provide them and the default values would be used instead. So for the method declaration of:

def order_food(price, size="large", quantity = 8)

you could make the following calls:

order_food price: 10, quantity: 2 #will use default size with value 'large'
order_food price: 5, size: 'small' #will use default quantity of 8

First of all, the variables are local variables not instance variables. Instance variables belong to the instance of a class and are notated with the @var_name notation, while local variables belong to a scope (very simplistically that is anything surrounded by a do ... end . More detail here ) and are notated with just the variable name ( my_var = "some_value" ).

It depends on what you are using the method for. If you want to be able to pass the arguments quantity and size than you should use the first one. The second one will give you and ArgumentError if you try to pass more than 1 argument. The first one will set the values of quantity = 8 and size = "large" if they are not passed, but if they are passed, it will use the passed values.

If you want to be able to call the method and set the size and quantity as arguments and if they are not passed use size = "large" and quantity = 8 as defaults, use the first method:

order_food "9.00" #=> "8 large burgers, coming right up!"
order_food "9.00", "small", 1 #=> "1 small salad, coming right up!"

The second method will not allow you to pass either of the other two arguments, and they will always be set so quantity = 8 and size = "large" . This has it's benefits, because sometimes you don't want the variables to be change-able with arguments. So with the second method:

order_food "9.00" #=> "8 large burgers, coming right up!"
order_food "9.00", "small", 1 #=> ArgumentError: wrong number of arguments (given 3, expected 1)

Here's a re-reworked version of your code that's more Ruby-like:

def order_food(price, size: :large, quantity: 1)
  extras =
    case (quantity)
    when 1
      "salad"
    else
      "Burgers"
    end

  "#{quantity} #{size} #{extra}, coming right up!"
end

puts order_food(2, :small, 8)

Doing display ( puts ) inside a method is often giving the method too much responsibility. Split out display concerns from the compositional ones. Maybe you want to write that to a file, or embed it in HTML. puts inside the method limits your options.

Also take advantage of keyword arguments if you want to have a number of them that are somewhat arbitrary in nature. This allows you to skip one and use the other without having code that has to re-specify defaults.

There are in fact 4 common ways to pass parameters to a function. Your first example is the most common one but you made a bad example I'm afraid. Your quantity is always 8, so the if is superfluous, also the parametere price isn't used so also superfluous This would be the same as the following

    def order_food price
      "8 large Burgers, coming right up!"
    end

But that is not your purpose I presume.

So, it would be something like this First method

def order_food1(size, quantity, price)
  extra = quantity == 1 ? :salad : :Burgers
  cost  = quantity * price
  "#{quantity} #{size} #{extra}, coming right up! Thats is #{cost} dollar please"
end

+ fast (see benchmarks)

+ everybody uses it and understands it in one glace

- you have to know the parameters and the order in which they are defined, you need to read the API for less common uses methods

- you must provide all the parameters

Next : using optional parameters with default values

def order_food2(size = "large", quantity = 1, price = 4)
  extra = quantity == 1 ? :salad : :Burgers
  cost  = quantity * price
  "#{quantity} #{size} #{extra}, coming right up! Thats is #{cost} dollar please"
end

+ also widely used

+ no need to use parameters if they are the default ones

- you still need to know the order and meaning of the parameters and if the last is used you need them all

Next : using a hash as parameter

def order_food3(opts = {})
  opts = {size: :large, quantity: 1, price: 4}.merge!(opts)
  extra = opts[:quantity] == 1 ? :salad : :Burgers
  cost  = opts[:quantity] * opts[:price]
  "#{opts[:quantity]} #{opts[:size]} #{extra}, coming right up! Thats is #{cost} dollar please"
end

- less used, your method self is a little harder to read

- slower

+ no need to know parameters you don't need, nor the order

+ the use of the method is more readable

Next : a simplified version of the previous method

def order_food4(size: :large, quantity: 1, price: 4)
  extra = :quantity == 1 ? :salad : :Burgers
  cost  = quantity * price
  "#{quantity} #{size} #{extra}, coming right up! Thats is #{cost} dollar please"
end

- slower

+ method itself and use of it are more readable

Which one is better ? Depends on personal taste and the situation. I use all of them and there is nothing in the design guides as far as I know that prefares one or the other. In practice you will even combine some of them. Parameters that are seldom changed are best given a default value and vice versa. Methods that are called many times (eg recursive ones) could benefit from the faster and less memory consuming method 1. In my opinion, readability is most important for a Ruby script, so if having a lot of parameters and feasable use method 3 or 4.

Some examples of use and benchmarks..

puts order_food1("large", 3, 4)
puts order_food2("large", 3, 4)
puts order_food3(size: "large", quantity: 3, price: 4)
puts order_food3
puts order_food4(size: "large", quantity: 3, price: 4)
puts order_food4

# 3 large Burgers, coming right up! Thats is 12 dollar please
# 3 large Burgers, coming right up! Thats is 12 dollar please
# 3 large Burgers, coming right up! Thats is 12 dollar please
# 1 large salad, coming right up! Thats is 4 dollar please
# 3 large Burgers, coming right up! Thats is 12 dollar please
# 1 large Burgers, coming right up! Thats is 4 dollar please

require 'benchmark'

Benchmark.bmbm do |x|
  x.report("order_food1    ")   { 10000.times { order_food1("large", 3, 12) }}
  x.report("order_food2    ")   { 10000.times { order_food2("large", 3, 12) }} # all parameters given
  x.report("order_food2_def")   { 10000.times { order_food2 }} # using default parameters
  x.report("order_food3    ")   { 10000.times { order_food3(size: "large", quantity: 3, price: 12) }} # all parameters given
  x.report("order_food3 def")   { 10000.times { order_food3 }} # using default parameters
  x.report("order_food4    ")   { 10000.times { order_food3(size: "large", quantity: 3, price: 12) }} # all parameters given
  x.report("order_food4 def")   { 10000.times { order_food3 }} # using default parameters
end

#                       user     system      total        real
# order_food1       0.015000   0.000000   0.015000 (  0.010420)
# order_food2       0.000000   0.000000   0.000000 (  0.010675)
# order_food2_def   0.016000   0.000000   0.016000 (  0.011007)
# order_food3       0.015000   0.000000   0.015000 (  0.020182)
# order_food3 def   0.016000   0.000000   0.016000 (  0.016954)
# order_food4       0.015000   0.000000   0.015000 (  0.020256)
# order_food4 def   0.000000   0.000000   0.000000 (  0.016968)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM