On Wed, 30 Apr 2003, Simon Vandemoortele wrote: > _Goal:_ An Addressbook class that can be reused. > > > _Initial Design and shortcomings:_ > > The way I see it, an addressbook is merely a (smart) container for > contacts. It is responsible for loading, storing, managing (adding, > deleting) and saving the contacts. Thus, the interface could be > something like: > > Addressbook > |- open(filename) > |- addContact(contact) > |- getContact(name) > `- removeContact(name) > > Contact > |-setName > |-getName > |-setAge > |-getAge > |-... > > This approach provides a lot of flexibility - you can change a > contact with a one-liner: > > myAddressbook.getContact('Simon Vandemoortele').setAge(25). > > However, it leaves the content of addressbook (the contacts) freely > editable by everyone. I hope we all agree to the fact that this is a > serious limitation to the interface. Because someone can change a > contact without the addressbook noticing, the addressbook cannot enforce > any invariant that depends on it's contacts content. i.e.: it can't > enforce a no-two-entries-with-the-same-name policy. Nor can we decide to > implement the addressbook as a sorted Array anymore. i would not agree with these statements. it is not impossible to maintain the referential integrity of the address book and to allow unfettered access to contacts. all that is required is a) contacts can be aware that they are 'contained' (in an address book) b) contact methods use this awareness when ri can be violated eg (silly demo impl). ----CUT---- class Addressbook < Array alias __append << def << contact check_contraint '<<', contact __append(contact) sort! contact.addressbook = self self end def get_contact name;map{|c| return c if c.name == name};end def check_contraint constraint, arg CONSTRAINTS[constraint].call self, arg end protected CONSTRAINTS = Hash[ 'Contact#name=' => Proc.new do |addressbook, name| raise 'name exists' unless addressbook.select{|contact| contact.name == name}.empty? end, '<<' => Proc.new do |addressbook, contact| raise 'contact exists' unless addressbook.select{|c| c.name == contact.name}.empty? end, ] end class Contact include Comparable attr :name attr_accessor :addressbook def initialize args;self.name=(args[:name]);end def name= name addressbook and addressbook.check_contraint 'Contact#name=', name @name = name end def <=> other; name <=> other.name; end end addressbook = Addressbook.new c0 = Contact.new :name => 'bob' c1 = Contact.new :name => 'bob' c2 = Contact.new :name => 'joe' addressbook << c0 << c0 rescue p $! ((addressbook << c2).get_contact('joe')).name = 'bob' rescue p $! ----CUT---- which will output: ~/eg/ruby > ruby addressbook.rb #<RuntimeError: contact exists> #<RuntimeError: name exists> this does, however, tightly couple contacts with addressbooks. but *aren't* they tightly coupled in the 'real' world? i can see alternatives to this type of impl, such as making to container itself enfore ri, such as class Addressbook < RBTree def << contact self[contact.name] = contact end end which would allow only one contact with the same name. alternatively, you could implement a module which enfores RI for mutable methods and extend any contact added to an addresssbook with this module - contacts outside of an addressbook do not need RI after all. in the end - if contacts are aware they are IN an addressbook i think you can have your cake (easy access) and eat it too (safe access). again, i think any design will require *some* coupling of contacts and addressbooks since contacts are *related* the *context* of an addressbook, but not outside of it (eg. contact 'bob' could be in addressbook A *and* in addressbook B). this is a very interesting topic IMHO - at least for anyone attempting to do OO desgin - and this is all ruby programmers right? ;-) and therefore on topic! -a -- ==================================== | Ara Howard | NOAA Forecast Systems Laboratory | Information and Technology Services | Data Systems Group | R/FST 325 Broadway | Boulder, CO 80305-3328 | Email: ara.t.howard / fsl.noaa.gov | Phone: 303-497-7238 | Fax: 303-497-7259 ====================================