重写 ruby 代码以删除与动态变量名称的重复

Rewrite ruby code to remove repetition with dynamic variable names

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

# 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 .

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

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

一般来说,如果您想使用动态变量名,那么答案是散列、数组或函数。在这种情况下,函数是合适的。使用 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. 要为其创建新对象的 class。
  2. 将哪个字段放入哈希。

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

第二个有点棘手。您可以传入一个说明如何转换为散列的函数。但这些都是对象,好好利用吧。不要告诉 class 如何从 CSV 加载它的对象,而是让 class 为您从 CSV 加载对象。这意味着绝对使它成为一种 class 方法。

要处理散列键的问题,请定义一个方法,说明如何获取 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 文件和 class。它将使用 class 的 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

这是一个很好的开始。