我闻到的最大味道不是嵌套循环,而是从每个模型中获取值的方式的近似重复。
让我们将这种重复提取到同名的类似方法中,
exported_values
在…上
Order
,
Move
和
Stop
:
class Order
def exported_values
exported_attributes[:order].map { |attrs| { public_send(attrs) }
end
end
class Move
def exported_values
order.exported_attributes[:stop].map { |attrs| { decorate.public_send(attrs) }
end
end
class Stop
def exported_values
move.order.exported_attributes[:move].map { |attrs| { decorate.public_send(attrs) }
end
end
并将其用于
to_csv
:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(first.exported_attributes.values.flatten) # headers
each do |order|
order_values = order.exported_values
order.moves.each do |move|
order_and_move_values = order_values + move.exported_values
move.stops.each do |stop|
csv << order_and_move_values + stop.exported_values
end
end
end
end
end
以上还有一些小的改进:
-
获取导出的值并将其连接到尽可能最外层的循环中,以提高效率。
-
循环移动和停止
each
而不是与
map
,因为循环是为了副作用而不是返回值。
-
删除不必要的使用
self.
.
现在
到csv
也没那么糟糕。但它仍然有一点
feature envy
(也就是说,它在其他对象上调用了太多方法),因此让我们将更多方法提取到模型上:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(first.exported_attributes.values.flatten) # headers
each { |order| order.append_to_csv(csv) }
end
end
class Order
def append_to_csv(csv)
values = exported_values
moves.each { |move| move.append_to_csv(csv, values) }
end
end
class Move
def append_to_csv(csv, prefix)
values = exported_values
stops.each { |stop| stop.append_to_csv(csv, prefix + values) }
end
end
class Stop
def append_to_csv(csv, prefix)
csv << prefix + exported_values
end
end
不再有嵌套循环。提取的方法有点重复,但我认为如果提取重复,就不清楚了。
接下来,我们可以尝试重构
导出值
方法转换为单个方法。
-
可能
Order#exported_attributes
可以分解为每个类上的一个方法,该方法不接受参数,只返回该类的导出属性。
-
这些方法之间的另一个区别是
顺序
不需要
.decorator
但其他的类是这样的。如果它有一个装饰器,只需使用它而不是实际的顺序;如果没有,就给它一个假的:
class Order
def decorator
self
end
end
然后可以定义单个
导出值
方法,并将其包含在所有三个类中:
def exported_values
exported_attributes.map { |attrs| { decorator.public_send(attrs) }
end
还有一个可能的改进:如果每个模型的导出值在实例的生存期内保持不变是可以的,您可以这样缓存它们
def exported_values
@exported_values ||= exported_attributes.map { |attrs| { decorator.public_send(attrs) }
end
并内联
values
当地人
append_to_csv
方法
和
在这些方法中,从父对象获取“前缀”,而不是将它们作为参数传递。
可能所有的新方法都应该提取到装饰器中,而不是提取到模型中;我不确定您的装饰器是用于CSV生成还是仅用于其他目的。