"Gavin Kistner" <gavin / refinery.com> schrieb im Newsbeitrag
news:4F71B514-0D6F-11D9-BA76-000A959CF5AC / refinery.com...
> Summary
> I'm looking for advice on how to design code so that I can dynamically
> add/remove instantiable classes, where each of these classes have
> values for a set of properties defined by a parent class.
>
> Details
> I'm starting to design/write an uber-ambitious home automation hub, in
> Ruby. In addition to having convenient things like a web-based GUI
> front-end, scheduler, and so on, the heart of this application is that
> it will (eventually) work with any automation hardware whose
> communication protocol is open (or reverse-engineered).
>
> The plan is to have 'adaptors' for each discrete hardware type, which
> encapsulate all the guts of the communication protocol and expose
> methods for that bit of hardware. The user can then 'instantiate' one
> or more of these adaptors, representing physical instances of that bit
> of hardware in the home.
>
> (For example, there's a single "Lutron RadioRA Wall Dimmer" adaptor,
> but the user may have 4 such switches in the house, named "Front
> Kitchen Lights", "Rear Kitchen Lights", "Bedroom Lights", and "Entry
> Lights".)
>
> In addition to custom methods, each adaptor needs to expose some common
> information, such as its name, manufacturer, category, sub-category,
> model number, and so on.
>
> I initially decided to have code like this:
>
> me = Foo::Bar::DeviceType.new( 'Lutron RadioRA Wall Dimmer', :Lutron,
> :Switches, :Dimmers, 'RA-ND6' )
> me.add_action( :turn_on, Proc.new{ ... } )
> me.add_action( :turn_off, Proc.new{ ... } )
> me.add_action( :level=, Proc.new{ ... } )
> me.add_action( :level, Proc.new{ ... } )
>
> #...and then later do something like...
> Foo::Bar::add_device( 'Entry Lights', blah )
> # ...where blah is a pointer to the DeviceType instance
>
>
> But then I realized that I'm mostly just putting a wrapper around a
> class definition. Further, I realized that I want all adaptors to
> support common methods (name, manufacturer, model number), all light
> switches to support some common methods (turn_on, turn_off), and all
> dimmers to support others still (level= and level). This reeks of a
> class hierarchy, so I was thinking that I might do something like:
>
> # Core app
> class Foo::Bar::DeviceType
> def name; raise "Implement Me!"; end
> def manufacturer; raise "Implement Me!"; end
> def model; raise "Implement Me!"; end
>
> class Light
> def turn_on; raise "Implement Me!"; end
> def turn_off; raise "Implement Me!"; end
>
> class Dimmer
> def level=(n); raise "Implement Me!"; end
> def level; raise "Implement Me!"; end

I'd define a method to easy this or use the approach shown below (stored
mandatory method names).

class Class
  def method_stub(m)
    class_eval { define_method(m) { raise "Implement Me!" } }
  end
end

class Base
  method_stub :foo
end

Base.new

>> Base.new.foo
RuntimeError: Implement Me!
        from (irb):19:in `foo'
        from (irb):19:in `foo'
        from (irb):25

> end
> end
> end
>
> #Adaptor file
> class Foo::Bar::DeviceType::Light::Dimmer::RadioRA
> def name; 'Lutron RadioRA Wall Dimmer'; end
> def manufacturer; 'Lutron'; end
> def name; 'RA-ND6'; end
> def turn_on; ...; end;
> def turn_off; ...; end;
> def level=(n); ...; end;
> def level; ...; end;
> Foo::Bar::DeviceType::add_device( self )
> end
>
> .. and the add_device method would
> a) Create a dummy parent class and run through all the methods to see
> if they throw errors.
> b) Add the class into a list of instantiable adaptors if it works.
>
> Questions
> 1) Is there a way to inspect the runtime state and figure out which
> subclasses exist for a given class?

Here are two ways to ensure that you know your subclasses recursively:

class Base_1
  def self.inherited(cl)
    (@children ||= []) << cl
    me = self
    class <<cl;self;end.class_eval { define_method(:inherited) {|cl2|
me.inherited cl2} }
  end

  def self.subclasses() @children end
end

class Base
  def self.inherited(cl)
    (@children ||= []) << cl
    def cl.inherited(cl2) superclass.inherited(cl2) end
  end

  def self.subclasses() @children end
end

class Sub1 < Base
end

class Sub2 < Sub1
end

class Sub3 < Sub2
end

p Base.subclasses


> 2) Is there a better way to force/detect if a subclass implements
> certain methods?

You could store a set of method names in the base class and add a check
method for sub classes.

require 'set'

class Base
  def self.inherited(cl)
    (@children ||= []) << cl
    def cl.inherited(cl2) superclass.inherited(cl2) end
  end

  def self.subclasses() @children end

  def self.set_mandatory_methods(*m)
    @mandatory = Set.new(m.flatten.map{|m| m.to_s})
  end

  def self.check_mandatory
    @children.each do |cl|
      diff = @mandatory - Set.new(cl.instance_methods)
      raise "Methods missing: class=#{cl} methods=#{diff.to_a.inspect}"
unless diff.empty?
    end
  end
end


> 3) Is there a better way overall to achieve my goal?

I don't have a different approach right now.  Sounds reasonable.

Kind regards

    robert