【问题标题】:In Ruby is there a way to overload the initialize constructor?在 Ruby 中有没有办法重载初始化构造函数?
【发布时间】:2010-10-18 11:38:55
【问题描述】:

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

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

在 Ruby 中是否有办法实现同样的结果:两个构造函数采用不同的参数?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    答案是肯定的和否定的。

    您可以使用多种机制获得与使用其他语言相同的结果,包括:

    • 参数的默认值
    • 变量参数列表(splat 运算符)
    • 将参数定义为哈希

    语言的实际语法不允许你定义一个方法两次,即使参数不同。

    考虑到以上三个选项,可以使用您的示例实现如下

    # 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
    

    您会在 Ruby 代码中经常遇到第二种机制,尤其是在 Rails 中,因为它提供了两全其美的优点,并允许一些语法糖生成漂亮的代码,尤其是不必将传递的哈希括在大括号中。

    wikibooks link 提供了更多阅读内容

    【讨论】:

    • 感谢您指出Person.new(:first_name => "...", :last_name => "...") 方法。出于某种原因,我并没有想到要使用它,但这确实回答了我的问题。
    • Ruby 2.0 支持开箱即用的命名参数:robots.thoughtbot.com/ruby-2-keyword-arguments
    【解决方案2】:

    我倾向于这样做

    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
    

    但我不知道这是否是最好的方法。

    【讨论】:

    • +1:不是最好的,但肯定很聪明。遵循 Ruby 的命名系统。
    • +1 表示聪明的想法,尽管这并不是每个定义都重载。
    【解决方案3】:
    class Person
      def initialize(name, lastName = nil)
        name = name + " " + lastName unless lastName.nil?
        @name = name
      end
    end
    

    【讨论】:

      【解决方案4】:
      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
      

      【讨论】:

        【解决方案5】:

        checkout functional-ruby gem,灵感来自 Elixir pattern matching 功能。

           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
        

        【讨论】:

        • 这是一种借鉴另一种语言实践的创新方式。我希望看到您的代码示例更类似于使用 Personfirst_namelast_name 的原始发帖人的语言。
        【解决方案6】:

        您可以使用konstructor gem 在 Ruby 中声明多个构造函数并模仿重载:

        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')
        

        【讨论】:

        • 这是一个被低估的答案。对于MyType.parse(foo)MyClass.decode(bar)等场景,除了默认构造函数外还使用konstructor非常有帮助。
        【解决方案7】:

        您可以在 initialize 方法中使用 double splat 运算符 **逻辑或(双管道)|| 来达到相同的效果.

        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">
        

        但是,如果在没有first_name 的情况下创建新的Person,则追加&lt;&lt; 操作将失败并显示NoMethodError: undefined method '&lt;&lt;' for nil:NilClass。这是一个重构的 initialize 方法来处理这种情况(如果 either 选项被排除,则使用 strip 删除空格)。

        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">
        

        事实上,这种方法处理调用Person.new 时不带参数或使用意外键返回新实例,并将@name 设置为空字符串:

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

        【讨论】:

          猜你喜欢
          • 2010-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-29
          • 1970-01-01
          • 1970-01-01
          • 2021-01-03
          • 1970-01-01
          相关资源
          最近更新 更多