On Fri, Sep 24, 2004 at 12:20:54PM +0900, Gavin Kistner wrote: > While I'm normally a huge duck-typing-plus fan, I'm waffling in this > case. I think the reason is that I hope to have adaptor modules > (classes) being contributed from many different authors, providing me > with two challenges: > > 1) Given the large number of devices which fit under 'automation', I > want a hierarchical organization of devices which may be available. If > I leave it up to each author, I fear the user will install an adaptor > for their hardware and find that it's under LightSwitch/Dimmer while > every other similar adaptor is in LightSwitches/Adaptors. Or worse, > it's in DimmerSwitches, or Electrical/Wall/Analog/Slider. If I understand rightly, I think the main constraint here is that you want to organise the classes logically for the user to see when selecting one (to create a new instance of a DimmerSwitch, for example, selecting it from a tree rather than a long linear scrolling list). You can always have a separate hierarchy for display purposes - or even multiple different hierarchies, or the same class popping up in multiple places in the same displayed hierarchy. It doesn't have to follow a class or module hierarchy, and although it could, I think it will limit your flexibility if you do. You might, for example, want to give one view organised by manufacturer, and another organised by device type. > and at the same time gives me a modicum of protection from > adaptor authors running over each others' code (through namespace > collisions). If that's a concern, then modules should be organised by vendor/author, not by function, so that each author is responsible for her own namespace. module ACMEwidgets class OnOffSwitch ... end end That's another reason for making the organisation of objects for display purposes separate from the module/class hierarchy (unless you're happy to organise the display by vendor). Of course, when you do "require 'acmewidgets'", you might want the classes to appear in the correct places in the GUI hierarchy automagically. To do that, I think they should export an "index card", rather like when a new book arrives at the library - the librarian looks at the index card to work out where to file it. To support that, you need to define your own classification system, and then modules can pick the one which suits them best. require 'homecontrol' module ACMEwidgets class OnOffSwitch def self.indexcard [Homecontrol::DevType::Switch, "ACME Widgets model 345X mains controller"] end end end Or you could use a constant with a well-known name within the class (the details of the exporting mechanism are unimportant, IMO) > 2) Similar to the above, the more leeway I give the authors in the > method naming convention, the more I fear that some switches will have > "Turn On", some will have "On", and some smartass authors will use > "Illuminate".Having thought about your points, I will certainly need to > look at the exposed methods for any adaptor and be able to use them. > (Who knows what bizarre functionality I will forget to include or > account for?) But, for the sake of consistency, I think I want to say > "Damnit, if you have a 'Light' class, you better have a :turn_on > method." Then you're imposing a duck-typing system anyway. You need to provide hard-written guidelines for module authors, for each sort of device, what methods it should have. Although really, I think that end users are not likely to be invoking methods on their devices directly (unless they are Hal), in which case the GUI can take care of it. > Well, as Robert correctly surmised, I don't actually want to implement > common code to them. (Some adaptor authors may want to, and they're > welcome to define their own modules if they wish). What I'm probably > going to end up with is something like: > > class Device > def self.mandatory_methods= ( *method_names ) > @mandatory = method_names.flatten.to_set > @mandatory.merge( superclass.mandatory_methods ) if > superclass.respond_to? :mandatory_methods > end > > def self.mandatory_methods > @mandatory > end > > self.mandatory_methods = [:name, :manufacturer, :models] > > class Light < self > self.mandatory_methods = [:turn_on, :turn_off] > class Dimmer < self > self.mandatory_methods = [:level=, :level] > end > end > > class Outlet < self > self.mandatory_methods = [:turn_on, :turn_off] > end > end > > along with a test for these methods as part of my #instance handling. Why, then, have these arrays of mandatory_methods? What exactly are you going to do if the class turns out not to have a method that you claim is mandatory? I guess you could write code which automatically mails the offending module back to the author and rm's it from the filesystem! :-) Seriously... I am a believer in "do the simplest thing which can possibly work". If the method doesn't exist - then when you call it, Ruby will raise an exception for you. There's no need to pre-list the methods expected, nor to do def turn_on raise "Not implemented!" end in a superclass, because that's essentially the default behaviour which Ruby has. As another example: you were talking about using introspection to work out which classes were subclasses of a top class, to identify which controllable objects exist in the system. Sure you can do that - but it might be simpler just to set a global variable which contains an array or hash of them (a hash indexed by name for the GUI to display). After all, how often do you add a new *class* of object to the system, like a new type of light switch (as opposed to a new instance of a light switch)? You'll probably edit a .rb file to add the appropriate "require" line anyway, so adding a new entry into a global list of usable object classes isn't hard. I suppose the introspection way is a little prettier: module Homecontrol module Controllable # dummy, just flags that this object is controllable end end ... module ACMEwidgets class OnOffSwitch include Homecontrol::Controllable DESCRIPTION = "ACME Widgets model 345X mains controller" end end ... klasses = [] ObjectSpace.each_object(Class) { |k| klasses << k if k.include? Homecontrol::Controllable } klasses.each { |k| puts k.const_get('DESCRIPTION') } But I'd think of that as cake-icing really. Cheers, Brian.