代码之家  ›  专栏  ›  技术社区  ›  Faisal

读写属性,带有默认值和验证

  •  1
  • Faisal  · 技术社区  · 8 年前

    我正在构建一个类来表示自动生成的玩家角色。角色有性别,可接受的价值观是 :male 和 :female . 我需要有一个属性读写器来允许访问和自定义,但也需要一个“默认”值,以防尚未选择某个值并且用户不想选择该值。我有以下代码:

    class Character
        attr :gender
    
        def gender= g
            g = nil unless g == :male or g == :female
            @gender = g || [:male, :female].sample
        end
    
        def gender
            @gender ||= gender=(nil)
        end
    end
    

    其思想是始终通过setter来确保该值是有效的,并使代码更易于维护,以防我决定在以后的性别列表中添加新值。我面临的问题是a)验证行有多余的值,下面有设置行(在gender=method中),b)从reader方法调用writer方法在“nil”传递时看起来很奇怪。有什么想法可以让这个更优雅吗?

    3 回复  |  直到 8 年前
        1
  •  3
  •   Raj    8 年前

    如果在构造函数中设置了有效值,则在getter中不需要setter。将来,如果要添加新的性别,可以一次性更新现有角色。这会让你的代码更干净。

    class Character
        attr :gender
    
        def initialize
           @gender = valid_genders.sample
        end
    
        def gender= g
           @gender = g if valid_genders.include? g
        end
    
        private
    
        def valid_genders
          [:male, :female]
        end
    
    end
    
        2
  •  1
  •   Josh Brody    8 年前

    这是 真的? 太天真了,也许是杀伤力太强了。如果你真的很想用一种干净时尚的方式来做这件事,并且计划拥有一个不仅仅是性别属性的东西,你可能会想投资于……

    module CoolAttributes
    
      def self.included(base)
        base.extend ClassMethods
      end
    
      module ClassMethods
        def attribute(name, type, options = {})
          get(name, type, options)
          set(name, type, options)
        end
    
        def get(name, type, options)
          define_method(name) do
            instance_variable_get(:"@#{name}") || if options[:default].is_a?(Proc)
                                                    options[:default].call
                                                  else
                                                    options[:default]
                                                  end
          end
        end
    
        def set(name, type, options)
          define_method("#{name}=") do |value|
            instance_variable_set(:"@#{name}", self.class.cast_to(type, value))
          end
        end
    
        def cast_to(type, value)
          if type == :boolean
            if ['false', 0, '0', false].include?(value)
              false
            else
              true
            end
          elsif type == :symbol
            value.to_s.to_sym
          end
        end
      end
    end
    
    class Character
      include CoolAttributes
    
      attribute :gender, :symbol, :default => :male
    end
    
    c = Character.new
    puts c.gender # male 
    c.gender = 'male'
    puts c.gender.class # Symbol
    

    这差不多就是 ActiveModel::Attributes 是。

    编辑:我最后用这个做了一个宝石。 https://github.com/joshmn/attributary

        3
  •  0
  •   iGian    8 年前

    我的版本,您也可以随意初始化:

    class Character
        attr :gender
    
        def initialize(g = nil)
          valid? g ? @gender = g : @gender = random_gender
        end
    
        def gender=(g)
          @gender = g if valid? g
        end
    
        def self.list_genders
          new.genders.each_with_index { |gender, idx| puts "#{idx+1} - #{gender}" }
        end
    
        def valid?(g)
          g && genders.include?(g)
        end
    
        def genders
          [:male, :female]
        end
    
        def random_gender
          genders.sample
        end
    end
    
    
    Character.list_genders
    character = Character.new
    p character.gender
    character = Character.new(:male)
    p character.gender
    character.gender = :female
    character.gender = :none
    p character.gender
    
    推荐文章