From: Simon Vandemoortele Date: 2003-05-01T01:55:07+09:00 Subject: [OO interface design] Pass-by reference VS encapsulation ? Dear ruby-ists, I hope you will indulge this slightly (I hope) off-topic design question since it is not specific to ruby design. _Setting:_ Suppose you want to code an addressbook editor. You want it to be based on *Addressbook* class that can be reused, so that other people may write their UI on your addressbook format. How do you go about this ? My suggestion would be: create a *Contact* class that contains one persons information (name, address, phone, ...). Then create a container for *Contact* instances and call it *Addressbook*. I imagine you would want the addressbook to be responsible for the way it loads, stores and saves its contacts and hide the implementation details from the world. One could write an interface like: class Addressbook def Addressbook.open(file) {...} (takes a block) def addContact(contact) {...} def getContact(contact_name) {...} def deleteContact(contact_name) {...} end _Problem:_ All this works wonderfully well but for one thing: once the addressbook gives a *Contact* (actually a reference to *Contact*) to a client, the client can modify the *Contact* (that is _inside_ the addressbook) without the addressbook's knowledge. In effect: the addressbook's content is open for the world to play with. Overall this could be pretty much OK if the addressbook didn't really care about the content of his contacts. Suppose the addressbook wants to enforce a 'no two contacts with same name' policy ? It can't because you can always enter an contact and change it's name afterwards (behind the addressbook's back). What if you want to implement the addressbook as an Array of contacts sorted by their names ? _Solution proposals:_ [1] The 'pass-by-value emulation' solution Emulate pass-by-value by returning a deepcopy of a contact when when 'getContact' is called. You would also have to take a deepcopy of any contact added with 'addContact' and implement a 'updateContact(contact)' function that allows one to supersede a contact with a new version. Contra: to be really safe you have to implement any visitor+iterator pattern so that the visitor/iterator sees deepcopy's of the contacts. (auch!) [2] The 'frozen contacts' solution Very easy: freeze all contacts that are put in the addressbook and implement an 'updateContact(contact)'. Contra: weird ? (feel 'hackish' to me) [3] The 'callback' solution When a contact is added to the addressbook, have the addressbook register itself as an observer of the contact's name with the contact (and have the contact notify the addressbook if it changes). Contra: strong coupling of Contact and Addressbook class [4] The 'give up abstraction layer' solution Maybe a container who's elements are easily accessible (visitor+iterator pattern) cannot have interface decoupled with it's implementation ? This would mean you might as well implement the Addressbook class as an array. Contra: - no hiding of implementation - 'no two contacts with same name' and other policies have to be implement by the clients What do you think ? Simon -- There are 10 types of people in the world... those who understand binary and those who don't.