From: David Masover Date: 2010-07-29T12:40:34+09:00 Subject: Re: implementing multiple "constructors" in ruby On Wednesday, July 28, 2010 07:54:03 pm travis.ml-ruby-talk@subspacefield.org wrote: > So I have a class that I may want to instantiate multiple ways. > > Here's the signatures of the some so far: > > def create_from_file(absolute_filename, archive_filename, fsg=nil, > verbose=false, debug=false) def create_from_parts(archive_filename, mtime, > sha512hash) > def create_from_string(str) Ah, that answers my question from earlier. Do an options hash. Five arguments is too many. Foo.new :absolute_filename => '/foo/bar', :archive_filename => 'bar' Foo.new :archive_filename => '/foo/bar', :mtime => 123, :hash => 'abc' Now you can trivially branch on them. > So I looked into this a bit, and I assume something like this could > work: > > def self.create_from_file(*args) > return self.new.create_from_file(*args) > end > > Then I realized that I've got six methods now to do initialization; > three class methods (which are a little obscure to noobs) They shouldn't be. They're just instance methods that happen to be on a Class object. Java's "static" methods screw it up by putting them in the same namespace, that's all. > and three > instance methods. That's a lot of verbosity... is there a better way? Sure: class Foo class << self %w(file parts string).map{|x| :"create_from_#{x}"}.each do |type| define_method type do |*args| new.send type, *args end end end end Ok, that's a little verbose. If this is a common pattern, you could extract it into a library so you could end up doing something like this: class Foo # implementation of the DefineMethods module left as an exercise # to the reader... include YourInitializers initializer :string do |str| ... end initializer :parts do |archive_filename, mtime, hash| ... end end > Then it dawned on me: > > def initialize(method=nil, *args) > case method > when :file then create_from_file(*args) > when :parts then create_from_parts(*args) > when :string then create_from_string(*args) # unused > # if no method, do no intialization > end > end If you're calling that directly, that's a little gross... > I don't have to do any retarded run-time type checking to > emulate polymorphism, In my last message, I suggested exactly that, but for a different reason. The main reason to do that would be convenience -- usually, you're testing between an options hash and something else that's clearly not a hash, and not much beyond that. But you're right to be cautious -- it gets ridiculous beyond one or two types. > PS: As you see above, I'm passing verbose and debug around a lot and > hate it. I'd like to use globals, but it seems kind of fugly to have > classes accessing globals which are only used when the __FILE__ == > $0. In fact, I always want verbose and debug off except when invoked > as a script. Is there any cleaner way to deal with this? What do you mean "as a script"? Maybe not globals, then. Maybe module-specific constants. For example, hopefully, with a project this large, you've got a single module (or class, but probably module) serving as a base namespace: module SomeBackupProgram DEBUG = false end Even better, you could do this: module A class << self attr_accessor :debug end module B class << self attr_writer :debug def debug instance_variable_defined?(:@debug) ? @debug : A.debug end end attr_writer :debug def debug instance_variable_defined?(:@debug) ? @debug : self.class.debug end end end I'm guessing there are libraries out there to make that less verbose, and it shouldn't be too hard to write one yourself. The idea is that you could then set it globally: A.debug = true Or override it locally, while leaving the global setting alone: A::B.debug = true Or override it per-instance: foo = A::B.new foo.debug = true But, if you leave it alone, you get one global switch to turn on and off, and it's available locally.