Factory design

Greetings!
A friend of mine has recently started using Ruby, and has run into a
little problem. He is trying to create different objects depending on
the contents of a string. His intuition is to use factory design for
this, and he'd like to know if there is a Ruby Way to do this.

Below is his original e-mail.
Regards...
-CWS

----8<----
Hello, I'm wondering if anyone can suggest a good Ruby idiom for the
following problem. I'm pretty new to Ruby and am migrating from
statically-typed languages like Java and C++.

I have a string representation of some data that I want to read from an input
source and load into a file. Based on what this input is, I want to create a
subclass.

The concrete example is that I have a number of "events" in a log file that I
want to be able to create based on the input I read. There are three kinds
of events: a Phase, an Interruption, and a Defect. Each one of them has a
beginning time, a duration, and a comment.

Phases can contain Interruptions and Defects. Defects can contain
Interruptions and other Defects.

In any case, I have an identifier on my log file that looks like "2005-01-04
23:34:12 begin_phase". If I see this, I want to create a "Phase" object. If
it says "2005-01-04 23:52:16 begin_interruption", then I want to create an
interruption instead.

My intuition is that this is a typical Factory Design Pattern, so I would
create a Ruby class method that creates the appropriate type based on the
results of the string and returns it. However, someone familiar with Ruby
told me that Ruby's dynamic typing and duck typing is such that this kind of
solution "feels incorrect".

Does anyone have suggestions as to what I can do? Thanks!

-- -- Irwin (ihkwan at gmail.com)

Generally, Ruby makes this kind of design trivial with the fact that classes are objects. Just pass the class object, it's a free factory! Here's an example:

  class PrinterA
    def print_something_very_cool
      puts "Using PrinterA!"
    end
  end

  class PrinterB
    def print_something_very_cool
      puts "Using PrinterB!"
    end
  end

  def engage_printer( printer )
    p = printer.new
    p.print_something_very_cool
  end

  engage_printer PrinterA
  engage_printer PrinterB

Hope that helps.

James Edward Gray II

···

On Apr 11, 2005, at 4:56 PM, Claus Spitzer wrote:

Greetings!
A friend of mine has recently started using Ruby, and has run into a
little problem. He is trying to create different objects depending on
the contents of a string. His intuition is to use factory design for
this, and he'd like to know if there is a Ruby Way to do this.

In any case, I have an identifier on my log file that looks like

"2005-01-04

23:34:12 begin_phase". If I see this, I want to create a "Phase"

object. If

it says "2005-01-04 23:52:16 begin_interruption", then I want to

create an

interruption instead.

All class objects are kept as constants of Object. In fact when you
write a class name in your script Ruby treats it like any other
constant (starts with a capital letters) - it's just in Object name
space so every object can access it. Thus you can get them like any
other constant in your script:

c:\>irb
irb(main):001:0> class Phase; end
=> nil
irb(main):002:0> s = "2005-01-04 23:34:12 begin_phase"
=> "2005-01-04 23:34:12 begin_phase"
irb(main):003:0> klass_name = s.match(/begin_(\w+)/).captures[0] rescue
nil
=> "phase"
irb(main):006:0> klass = Object.const_get(klass_name.capitalize) if
klass_name
=> Phase
irb(main):007:0> o = klass.new
=> #<Phase:0x2d5f1b0>

As a further note, when you come to use the klass variable at runtime
remember that case statements use the #=== method which is slightly
different for Class objects - it tests if an object is an instance of
the the class:

case klass
  when Phase
   puts 'new phase'

  when Object
    puts 'something else'
end

Will print: 'something else'.
You need something like:

case klass.name
  when 'Phase'
   puts 'new phase'

  when 'Object'
    puts 'something else'
end

Which will print: 'new phase'.

HTH,
Assaph

Hi --

Greetings!
A friend of mine has recently started using Ruby, and has run into a
little problem. He is trying to create different objects depending on
the contents of a string. His intuition is to use factory design for
this, and he'd like to know if there is a Ruby Way to do this.

Below is his original e-mail.
Regards...
-CWS

----8<----

In any case, I have an identifier on my log file that looks like "2005-01-04
23:34:12 begin_phase". If I see this, I want to create a "Phase" object. If
it says "2005-01-04 23:52:16 begin_interruption", then I want to create an
interruption instead.

My intuition is that this is a typical Factory Design Pattern, so I would
create a Ruby class method that creates the appropriate type based on the
results of the string and returns it. However, someone familiar with Ruby
told me that Ruby's dynamic typing and duck typing is such that this kind of
solution "feels incorrect".

Does anyone have suggestions as to what I can do? Thanks!

I wouldn't worry about the matter of duck typing here. The concept of
duck typing, as I understand it, is essentially an explanatory tool
for getting people to understand, visualize, and make use of the fact
that class and type are not the same as each other in Ruby (a fact
that can be summed up very quickly but that in fact has huge
ramifications). Duck typing doesn't mean you never instantiate
objects of a particular class, nor that you never parse a string :slight_smile:

For this project, I don't think any (webbed) toes will be stepped on
if you do something like:

   require 'scanf'

   File.open("input.dat") do |fh|
     fh.scanf("%d-%d-%d %d:%d:%d begin_%s") do |y,mo,d,h,mi,s,klass|
       c = Object.const_get(klass.capitalize).new
       # etc.
     end
   end

(to illustrate with a simple line-by-line treatment).

David

···

On Tue, 12 Apr 2005, Claus Spitzer wrote:

--
David A. Black
dblack@wobblini.net

Hi,

At Tue, 12 Apr 2005 06:56:09 +0900,
Claus Spitzer wrote in [ruby-talk:137841]:

A friend of mine has recently started using Ruby, and has run into a
little problem. He is trying to create different objects depending on
the contents of a string. His intuition is to use factory design for
this, and he'd like to know if there is a Ruby Way to do this.

I remember that I'd written this library and posted somewhere
once.

module Factory
  def factory(c = Proc.new)
    (@__factory ||= ) << c
  end

  def create(*args)
    n = args.size
    @__factory.reverse_each do |f|
      next if (a = f.arity) < 0 ? n < ~a : n != a
      return f if f = f.call(*args)
    end if defined?(@__factory)
    nil
  end

  def inherited(klass)
    factory(klass.method(:create))
    super(klass)
  end
end

if $0 == __FILE__
  class A
    extend Factory

    def initialize(obj)
      @obj = obj
    end

    factory(method(:new))
  end

  class B < A
    factory do |obj|
      new(obj) if obj.respond_to?(:split)
    end
  end
  class B1 < B
    factory do |obj|
      new(obj) if obj.respond_to?(:string)
    end
  end
  class C < A
    factory do |obj|
      new(obj) if obj.respond_to?(:readline)
    end
  end

  p A.create("")
  require 'stringio'
  s = StringIO.new
  p A.create(s)
  p B.create(s)
end

···

--
Nobu Nakada

A more complex, but still simple example:

class Microwave # defrosts food
  attr_accessor :creators
  def initialize()
    @creators = []
  end
  def mode(key = nil, cls = nil)
    delegate = cls ? (Proc.new {|path| cls.new(path)}) : Proc.new
    creators << [key, delegate]
  end

  def defrost(path)
    r = nil
    creators.each do |(key, delegate)|
      next unless (case key
        when nil then true
        when Regexp then key =~ path
        when String then key == path
        else false
      end)
      return r if (r = delegate.call(path))
    end
  end
end

# A delectable bit wrap. Whole wheat pita!
class FileWrap
  attr_accessor :contents
  def initialize(path)
    @path = path
    @contents = IO.read(path)
  end
  def method_missing(sel, *args)
    r = @contents.send(sel, *args)
    if sel.to_s[-1..-1] == '!'
      (File.open(@path, 'w') {|f| f.write(@contents)})
    end
    r
  end
end

M = Microwave.new

M.mode(/\.txt$/, FileWrap)
M.mode(/\.ruby_object$/) {|path| Marshal.load(IO.read(path))}

# Put some stuff in the fridge
File.open('/tmp/a_hash.ruby_object', 'w') do |f|
  f.write(Marshal.dump(:a => 2, 'a' => 'two'))
end
File.open('/tmp/a_file.txt', 'w') do |f|
  f.write('Hello World')
end

h = M.defrost('/tmp/a_hash.ruby_object')
puts h['a'] #=> two
puts h[:a] #=> 2

f = M.defrost('/tmp/a_file.txt')
puts f.contents #=> Hello World
f.gsub!('World', 'Food')
puts `cat /tmp/a_file.txt` #=> Hello Food

Well, it was simple. Then I became carried away.

···

--

Nicholas Seckar aka. Ulysses

"David A. Black" <dblack@wobblini.net> schrieb im Newsbeitrag
news:Pine.LNX.4.61.0504111508240.7304@wobblini...

Hi --

> Greetings!
> A friend of mine has recently started using Ruby, and has run into a
> little problem. He is trying to create different objects depending on
> the contents of a string. His intuition is to use factory design for
> this, and he'd like to know if there is a Ruby Way to do this.
>
> Below is his original e-mail.
> Regards...
> -CWS
>
> ----8<----
>
> In any case, I have an identifier on my log file that looks like

"2005-01-04

> 23:34:12 begin_phase". If I see this, I want to create a "Phase"

object. If

> it says "2005-01-04 23:52:16 begin_interruption", then I want to

create an

> interruption instead.
>
> My intuition is that this is a typical Factory Design Pattern, so I

would

> create a Ruby class method that creates the appropriate type based on

the

> results of the string and returns it. However, someone familiar with

Ruby

> told me that Ruby's dynamic typing and duck typing is such that this

kind of

> solution "feels incorrect".
>
> Does anyone have suggestions as to what I can do? Thanks!

I wouldn't worry about the matter of duck typing here. The concept of
duck typing, as I understand it, is essentially an explanatory tool
for getting people to understand, visualize, and make use of the fact
that class and type are not the same as each other in Ruby (a fact
that can be summed up very quickly but that in fact has huge
ramifications). Duck typing doesn't mean you never instantiate
objects of a particular class, nor that you never parse a string :slight_smile:

For this project, I don't think any (webbed) toes will be stepped on
if you do something like:

   require 'scanf'

   File.open("input.dat") do |fh|
     fh.scanf("%d-%d-%d %d:%d:%d begin_%s") do |y,mo,d,h,mi,s,klass|
       c = Object.const_get(klass.capitalize).new
       # etc.
     end
   end

(to illustrate with a simple line-by-line treatment).

If one needs more flexibility (for example because several tags should
create instances of the same class or whatever) then a hash in between
serves well:

FACTORIES = {
  "phase" => Phase,
  "interruption" => Interruption,
  # ...
}

if /begin_(\w+)/ =~ line
  obj = FACTORIES[$1].new
end

Or, even more flexible

FACTORIES = {
  "phase" => lambda {|line| Phase.new line},
  "interruption" => lambda {|*| Interruption.new},
  # ...
}

if /begin_(\w+)/ =~ line
  obj = FACTORIES[$1].call line
end

Kind regards

    robert

···

On Tue, 12 Apr 2005, Claus Spitzer wrote:

Thanks to everyone who replied - My friend really appreciates the help.
Cheers!

···

On Apr 11, 2005 6:50 PM, Nicholas Seckar <nseckar@gmail.com> wrote:

A more complex, but still simple example:

class Microwave # defrosts food
  attr_accessor :creators
  def initialize()
    @creators =
  end
  def mode(key = nil, cls = nil)
    delegate = cls ? (Proc.new {|path| cls.new(path)}) : Proc.new
    creators << [key, delegate]
  end

  def defrost(path)
    r = nil
    creators.each do |(key, delegate)|
      next unless (case key
        when nil then true
        when Regexp then key =~ path
        when String then key == path
        else false
      end)
      return r if (r = delegate.call(path))
    end
  end
end

# A delectable bit wrap. Whole wheat pita!
class FileWrap
  attr_accessor :contents
  def initialize(path)
    @path = path
    @contents = IO.read(path)
  end
  def method_missing(sel, *args)
    r = @contents.send(sel, *args)
    if sel.to_s[-1..-1] == '!'
      (File.open(@path, 'w') {|f| f.write(@contents)})
    end
    r
  end
end

M = Microwave.new

M.mode(/\.txt$/, FileWrap)
M.mode(/\.ruby_object$/) {|path| Marshal.load(IO.read(path))}

# Put some stuff in the fridge
File.open('/tmp/a_hash.ruby_object', 'w') do |f|
  f.write(Marshal.dump(:a => 2, 'a' => 'two'))
end
File.open('/tmp/a_file.txt', 'w') do |f|
  f.write('Hello World')
end

h = M.defrost('/tmp/a_hash.ruby_object')
puts h['a'] #=> two
puts h[:a] #=> 2

f = M.defrost('/tmp/a_file.txt')
puts f.contents #=> Hello World
f.gsub!('World', 'Food')
puts `cat /tmp/a_file.txt` #=> Hello Food

Well, it was simple. Then I became carried away.

--

Nicholas Seckar aka. Ulysses