在 Ruby 中是否有一种方法可以重载 initialize 构造函数?

在 Java 中你可以重载构造函数:

public Person(String name) {
this.name = name;
}
public Person(String firstName, String lastName) {
this(firstName + " " + lastName);
}

在 Ruby 中有没有一种方法可以实现同样的结果: 两个带不同参数的构造函数?

48841 次浏览
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
@name = name
end
end

The answer is both Yes and No.

You can achieve the same result as you can in other languages using a variety of mechanisms including:

  • Default values for arguments
  • Variable Argument lists (The splat operator)
  • Defining your argument as a hash

The actual syntax of the language does not allow you to define a method twice, even if the arguments are different.

Considering the three options above these could be implemented with your example as follows

# As written by @Justice
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
@name = name
end
end




class Person
def initialize(args)
name = args["name"]
name = name + " " + args["lastName"] unless args["lastName"].nil?
@name = name
end
end


class Person
def initialize(*args)
#Process args (An array)
end
end

You will encounter the second mechanism frequently within Ruby code, particularly within Rails as it offers the best of both worlds and allows for some syntactic sugar to produce pretty code, particularly not having to enclose the passed hash within braces.

This wikibooks link provides some more reading

I tend to do

class Person
def self.new_using_both_names(first_name, last_name)
self.new([first_name, last_name].join(" "))
end


def self.new_using_single_name(single_name)
self.new(single_name)
end


def initialize(name)
@name = name
end
end

But I don't know if this is the best approach.

class StatementItem
attr_reader :category, :id, :time, :amount


def initialize(item)
case item
when Order
initialize_with_order(item)
when Transaction
initialize_with_transaction(item)
end
end


def valid?
!(@category && @id && @time && @amount).nil?
end


private
def initialize_with_order(order)
return nil if order.status != 'completed'
@category = 'order'
@id = order.id
@time = order.updated_at
@amount = order.price
end


def initialize_with_transaction(transaction)
@category = transaction.category
@id = transaction.id
@time = transaction.updated_at
@amount = transaction.amount
end


end

You can use konstructor gem to declare multiple constructors in Ruby and imitate overloading:

class Person
def initialize(name)
@name = name
end


konstructor
def from_two_names(first_name, last_name)
@name = first_name + ' ' + last_name
end
end


Person.new('John Doe')
Person.from_two_names('John', 'Doe')

checkout functional-ruby gem which is inspired by Elixir pattern matching features.

   class Person
include Functional::PatternMatching


defn(:initialize, String) { |name|
@name = name
}


defn(:initialize, String, String) {|first_name, last_name|
@name = first_name + ' ' + last_name
}
end

You could use the double splat operator ** in conjunction with logical or (double pipes) || inside the initialize method to achieve the same effect.

class Person
def initialize(**options)
@name = options[:name] || options[:first_name] << ' ' << options[:last_name]
end
end


james = Person.new(name: 'James')
#=> #<Person @name="James">


jill_masterson = Person.new(first_name: 'Jill', last_name: 'Masterson')
#=> #<Person @name="Jill Masterson">

However, if a new Person is created without a first_name, then the append << operation will fail with NoMethodError: undefined method '<<' for nil:NilClass. Here is a refactored initialize method to handle this case (using strip to remove whitespace if either option is excluded).

class Person
def initialize(**options)
@name = options[:name] || [ options[:first_name] , options[:last_name] ].join(' ').strip
end
end


goldfinger = Person.new(last_name: 'Goldfinger')
#=> #<Person @name="Goldfinger">


oddjob = Person.new(first_name: 'Oddjob')
#=> #<Person @name="Oddjob">

In fact, this approach handles calling Person.new without arguments or with an unexpected key to return the new instance with @name set to an empty string:

nameless = Person.new
#=> <#Person @name="">


middle_malcom = Person.new(middle_name: 'Malcom')
#=> <#Person @name="">