【问题标题】:Rewrite ruby code to remove repetition with dynamic variable names重写 ruby​​ 代码以删除动态变量名的重复
【发布时间】:2017-05-21 05:16:58
【问题描述】:

我正在学习 OOD,并且我一直在将一些代码重组为类。我有三段相同的代码,它们打开一个文件并将内容输入哈希:

# Build grade objects and insert into hash
pass_fail_array = load_csv(pass_fail_file)
grade_collection = pass_fail_array.map{ |e| Grade.new(e) }

grade_hash = {}

grade_collection.each do |x|
    grade_hash[x.mark.to_s] = x
end

# Build student objects and insert into hash
student_array = load_csv(student_file)
student_collection = student_array.map{ |e| Student.new(e) }

student_hash = {}

student_collection.each do |x|
    student_hash[x.full_name] = x
end

# students = {:array_name = "student_array",}

# Build course objects and insert into hash
course_array = load_csv(course_catalog_file)
course_collection = course_array.map{ |e| Course.new(e) }

course_hash = {}

course_collection.each do |x|
    course_hash[x.course.to_s] = x
end

当我第一次尝试将其重写为一种方法时,我不确定如何命名我正在生成的集合 - grade_hashcourse_hashstudent_hash

我想,可能这应该是一个类,因为这段代码是关于生成集合副本的?看看在这种特定情况下如何应用一般原则会很有帮助

【问题讨论】:

    标签: ruby oop dry


    【解决方案1】:

    如果解决方案是动态变量名,那么您现在有两个问题。

    一般来说,如果您想使用动态变量名,答案是哈希、数组或函数。在这种情况下,函数是合适的。使用extract method refactoring

    注意:我的 Ruby 生锈了,为编码错误道歉。我将基本上保留算法,因为那不是重点。

    从重复的代码块之一开始。

    # Build grade objects and insert into hash
    pass_fail_array = load_csv(pass_fail_file)
    grade_collection = pass_fail_array.map{ |e| Grade.new(e) }
    
    grade_hash = {}
    
    grade_collection.each do |x|
        grade_hash[x.mark.to_s] = x
    end
    

    通过将其包装在函数声明中并返回代码块的乘积将其转换为函数:grade_hash

    def load_from_csv()
        pass_fail_array = load_csv(pass_fail_file)
        grade_collection = pass_fail_array.map{ |e| Grade.new(e) }
    
        grade_hash = {}
    
        grade_collection.each do |x|
            grade_hash[x.mark.to_s] = x
        end
    
        return grade_hash
    end
    

    注意哪些变量是在函数外声明的,只需pass_fail_file,所以传入。

    def load_from_csv(file)
        pass_fail_array = load_csv(file)
        grade_collection = pass_fail_array.map{ |e| Grade.new(e) }
    
        grade_hash = {}
    
        grade_collection.each do |x|
            grade_hash[x.mark.to_s] = x
        end
    
        return grade_hash
    end
    

    用函数调用替换代码。

    grade_hash = load_from_csv(pass_fail_file)
    

    提取方法是去除重复的第一步,也是死记硬背的一步。


    现在我们需要尝试使该功能适用​​于其他情况。每个代码块只有两点不同...

    1. 为其创建新对象的类。
    2. 将哪个字段放入哈希中。

    第一个很简单,你可以传入类名。这表明这可能作为类方法起作用。

    第二个有点棘手。您可以传入一个说明如何转换为哈希的函数。但这些都是对象,好好利用。与其告诉班级如何从 CSV 加载其对象,不如让班级为您从 CSV 加载对象。这意味着绝对要让它成为一个类方法。

    要处理散列键的问题,定义一个方法,说明如何获取 CSV 散列的键并使用它。

    # In each class define how to get the key for the CSV
    def csv_key
        return mark.to_s
    end
    
    # In a mixin, put a generic way to load from a CSV
    def self.load_from_csv(file)
        from_csv = load_csv(file)
        objs = from_csv.map{ |e| new(e) }
    
        objs.each do |x|
            hash[x.csv_key] = x
        end
    
        return hash
    end
    

    这种方法可能做的太多了,一分为二。一种用于从 CSV 加载对象,另一种用于将对象数组转换为哈希。

    def self.load_from_csv(file)
        return load_csv(file).map{ |e| self.new(e) }
    end
    
    def self.hash_from_objects(objs)
        objs.each do |x|
            hash[x.csv_key] = x
        end
    
        return hash
    end
    

    那么……

    grades = Grade.hash_from_objects(
        Grade.load_from_csv(pass_fail_file)
    )
    
    students = Student.hash_from_objects(
        Student.load_from_csv(student_file)
    )
    
    courses = Course.hash_from_objects(
        Course.load_from_csv(course_catalog_file)
    )
    

    这不是一个很好的界面,但您可以看到它是如何从告诉对象要做什么的procedural programming 转移到您询问对象要做什么的对象界面做。


    下一步是真正考虑将加载对象与正在加载的对象分开。

    请注意,该函数对其正在加载的对象几乎一无所知。这表明下一步将是创建一个Factory class 以从 CSV 加载对象,而不是将其作为对象接口本身的一部分。 CSV 加载器工厂对象会知道 CSV 文件和类。它将使用类的csv_key 方法。

    class CSVLoader
        attr_reader :file, :class
    
        def load
            hash = {}
            load_csv(@file).map{ |e| @class.new(e) }.each do |x|
                hash[ x.csv_key ] = x
            end
    
            return hash
        end
    end
    
    grades = CSVLoader.new( file: pass_fail_file, class: Grade ).load
    students = CSVLoader.new( file: student_file, class: Student ).load
    courses = CSVLoader.new( file: course_catalog_file, class: Course ).load
    

    这是一个很好的开始。

    【讨论】:

    • 非常感谢,这很有帮助。我可以看到您是如何一步步完成的,并且您链接的资源很棒。我可以开始看到如何通过接口思考的应用。起初我对加载行为是否应该作为一个类感到困惑,因为我不认为我想要它的多个实例,但是你逐步完成它的方式帮助我更多地了解如何应用“工厂类” ' 模式,也谢谢你
    猜你喜欢
    • 2021-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-06
    • 2023-03-19
    • 2011-04-12
    • 1970-01-01
    相关资源
    最近更新 更多