Because I like writing a small amount of code that solves a large number of problems, I really don't like coding special cases unless it is necessary. For instance, many people write scaffolding forms that allow you to edit a single object using all of its fields. But what happens if you just want to change the price and title of ten products using a single form? Of course, you could hand code that, or you could just pass a property name list to your form (the fields to include in this instance) and automatically make all of your forms support editing of a collection of n-records (including the special and most common case - where n=1).
This issue has come up as I try to decide how to implement my business objects. I need to have a base business object with generic getters and setters, and I need a collection object (I used to call it an Iterating Business Object) with my updated iterating methods - first(), next() and hasMore(). . . .
So, how do we implement this? Well, we could have (say) a Product object that extends BaseBusinessObject, but then if I want to return more than 1 product, I'm going to have to decorate the Product object with a Collection object and create a bunch of delegates to replicate all of the methods in the Product object. This works, but increases the complexity of the creation code (now my service methods or LigthWire needs to be really knowledgeable about how to decorate a business object with an iterator) and it adds extra code and breaks DRY because the collection needs to know and replicate the methods of the business object.
Of course, we could be smart and use introspection of the business object (for each method in businessobject create delegate) to dynamically generate the necessary delegates automatically at runtime so we wouldn't have the additional code, but that might confuse the unwary and because there is no way to define a function in ColdFusion without writing it to the hard drive, it'd be a bit of a hack.
I'm tempted instead to have Product extend BaseCollection (which extends BaseBusinessObject) which means I just need to create Product object and don't have to worry about decoration or delegation.
Aha, you say but that is evil for it fails the "IS A" test. You are right. If you think of a Product as a business object, it is clearly *not* a collection and so it would be evil to extend BaseCollection - composition as outlined above (specifically decoration with the iterator being composed of the business object) would be the right way to go.
However, if you think of a Product as a collection of n-products where n is sometimes 1 then it is perfectly appropriate for ProductCollection to extend BaseCollection. This also gives a bunch of benefits in terms of code reuse as I can then use the same code to display and process 0..n records as to display 1 (while still being able to use different code when necessary).
The more I think about this approach, the less I like it. I really don't want to go around calling my business objects UserCollection and ProductCollection and it arguably violates the Single Responsibility Principle of doing one thing well (although it depends on whether you argue that a Product is a product or a collection of products).
For now I'm just going to make Product and User extend BaseCollection, but I'll refactor as soon as I get a better idea for how to implementing this without creating more problems than I solve.
Any ideas/input wildly appreciated!
[UPDATE] At the moment, I've created a BaseGetSetObject with generic getters and setters used by both business objects and things like the Input object. That is extended by BaseBusinessObject which has iterating methods (first(), next() and hasMore()). Then the business objects (Product, user, etc.) extend that. While this seems a little hacky at first, I really do like the fact that I can rely on the iterator methods so if I want to write display or processing code it that supports 0..n objects (including the special case where n=1) I can do so. It takes a little mental step to think of business objects as fundamentally being collections of 0..n objects, but when you do so it really does simplify the code base in a number of places.
Will be interesting to see feedback as I'd characterize this as one of the "most easily criticizable" decisions, but when I get off my architectural high horse, it just seems to flow and work nicely within the code. Looking forward to anyone elses thoughts!