From: Robert Klemme Date: 2004-11-24T00:33:03+09:00 Subject: Re: Generalizing and Organizing Code "trans. (T. Onoma)" schrieb im Newsbeitrag news:200411230955.18950.transami@runbox.com... > I am finding it difficult to properly generalize and organize my code. Perhaps > others can offer some advice. Here is a snip two related parts. I have about > five others like these: > > # section > def parse_section_set_tab? ; false ; end > def parse_section_set_tab ; tab ; end > def parse_section?( line, tab ) > true > end > def parse_section_skip? ; false ; end > def parse_section( block ) > text = block.collect{ |line| line.text }.join("\n") > md = /([=]+)[ ]*(.*?)[ ]*[=]+/.match( text ) > raise "Unexpeted match error" unless md > level = md[1].length > title = md[2] > dobj = ArtML::DOM::Section.new( level, title ) > > until level > @document_stack.last.level do > @document_stack.pop > end > @document_stack.last << dobj # add this section to its parent > @document_stack << dobj # and pop it on > end > > > # paragraph > def parse_paragraph_set_tab? ; true ; end > def parse_paragraph_set_tab( line, tab ) ; line.indent ; end > def parse_paragraph?( line, tab ) > line.kind != :paragraph or line.indent > tab > end > def parse_paragraph_skip? ; false ; end > def parse_paragraph( block ) > text = block.collect{ |line| line.text }.join("\n") > content = text #parse_inline( text ) > @document_stack.last << ArtML::DOM::Paragraph.new( content ) > end > > In the generalized code these are called using #send. Eg. > > if send("parse_#{state}?", line, tab) > send("parse_#{state}", block) > end > > As you can see, I am using a name prefix "trick" to encapsulate these. I > would like to encapsulate them better, but I have a couple of difficulties. > My first problem is the use of @document_stack which they all work on. The > second problem is that I'm not sure what the encapsulation would be --they > consist of both data and code (but not state), I'd keep the option to put state in there, because you might need it some time. > yet there is only ever one of > each, so they aren't classes. But you can make them classes nevertheless. > And yet they don't seem like objects either b/c > the code differs between each of them. Perhaps use a module? That only seems > to adjust the name trick (Paragraph::parse instead of paragraph_parse). Plus, > I then have to do 'class << self', which seems ugly to me. Nor does it seem > like that's how a "module" is meant to be used. > > What is the proper way to handle this in a class-based OOPL? If I'd strive for the most OO solution I'd do this: class Parser class BaseProc def self.children() @children ||= [] end def self.inherited(cl) self.children << cl end attr_accessor :parent def initialize(parent) self.parent = parent end def parse(*a) false end end class Section < BaseProc def parse(line, tab) # ... end end class Paragraph < BaseProc def parse(line, tab) # ... end end BaseProc.children.each do |cl| define_method(cl.name.downcase.gsub(/^.*::/, '')) { @handlers[cl] } end attr_accessor :document_stack attr_reader :section, :paragraph def initialize @handlers = Hash.new {|h,k| h[k]=k.new(self)} end end p = Parser.new p.section.parse "s","" p.paragraph.parse "p", "" But this one does work, too class Parser attr_reader :section, :paragraph def initialize @section = lambda { |*a| puts "section: @foo=#{@foo} args=#{a.inspect}" } @paragraph = lambda { |*a| puts "paragraph: @foo=#{@foo} args=#{a.inspect}" } @foo = "something" end end p = Parser.new p.section.call "s" p.paragraph.call "p" My 0.02EUR... Kind regards robert