Ruby design question
Overview
I am writing a Ruby program that uses data from mysql queries to create chart URLs. A new requirement has recently surfaced where we could need to created graphs with grouped bars in the future. So instead of having one set of data, I could have any number of sets. Right now the constructor for my BarChart object only takes a single array of data, and I am looking Ruby-like ways of allowing more than one array of data.
Current Constructor
#constructor
#title The title of the graph
#data The data that will go in the bar chart
#labels The labels that match the data
#x_axis_label The label for the x axis
#y_axis_label The label for the y axis
def initialize(title, data, labels, x_axis_label, y_axis_label)
@title, @data1, @labels, @x_axis_label, @y_axis_label =
title, data, labels, x_axis_label, y_axis_label
super(@title, @@type, @@size)
@url = to_url()
end
My attempt
My initial thought was to use var args.
#co开发者_如何学编程nstructor
#title The title of the graph
#data The data that will go in the bar chart
#labels The labels that match the data
#x_axis_label The label for the x axis
#y_axis_label The label for the y axis
def initialize(title, *data, labels, x_axis_label, y_axis_label)
.....
end
Is this a decent idea? or is there a better way to go about it?
Thanks
Personally, when you have this many arguments, I would use an options hash.
def initialize(options = {})
options = { default options here, if applicable }.merge(options)
...
end
So that you can construct your class like this:
MyClass.new(:title => "Awesome Graph", :data => [[1,2,3], [4,5,6]], ...)
I find this approach makes your method calls much more readable, so you don't have a constructor call which has a long sequence of number and string arguments for which the meaning may be difficult to determine. This also gives you a natural way to add an arbitrary amount of optional parameters with default values.
EDIT: Added ruby version dependencies
This is a decent idea but sadly it won't work in 1.8.7.
In 1.8.7. they arbitrary argument exploder (*data
) only works at the end of the argument list. And in 1.9.2 only works if there are no optional parameters.
So something more ruby compliant like this might work,
def initialize(title, labels, x_axis_label, y_axis_label, *data)
.....
end
However, why not simply keep data where it is and do some duck typing to see how much data is actually stored in the array. The method signature wouldn't change, but the call would slightly to,
Object.new(title, [data1, data2, data3], labels, ... )
Like Jeremy I use an option hash.
In Addition I define defaults and required keys. Depending on my requirements I log missing/additional keys or I raise an exception. My Testexample code below just writes a message on stdout.
class X
DEFAULTS = {
p1: :default1,
p2: :default2
}
OBLIGATORY_PARAMETERS = [:p1]
#Parameters:
# p1
# p2
def initialize(options = {})
(OBLIGATORY_PARAMETERS - options.keys).each{|key|
puts "Missing key #{key}"
}
(options.keys - OBLIGATORY_PARAMETERS - DEFAULTS.keys).each{|key|
puts "Undefined key #{key}"
}
@options = DEFAULTS.merge(options)
end
end
#Ok
X.new( p1: 1 )#<X:0xc8c760 @options={:p1=>1, :p2=>:default2}>
#Missing key warnings
X.new( ) ##<X:0xc8c960 @options={:p1=>:default1, :p2=>:default2}>
X.new( p2: 2 )#<X:0xc8c5c0 @options={:p1=>:default1, :p2=>2}>
#Undefined parameter -> Exception
p X.new( p3: 3 )#<X:0xc8c5c0 @options={:p1=>:default1, :p2=>2}>
精彩评论