Circular objects - Forum - OpenEdge Development - Progress Community
 Forum

Circular objects

  • I have a "contact" model object (attributes only, no methods). This object holds the details of a contact (name, phone number etc)

    I also have a "client" model object, and in this object I have a variable array of contact objects, so I can iterate through all the contacts of the client.

    However, given a contact id, I want to find the client / clients that this contact is assigned to. So, it would make sense to create a variable array of client objects within the contact model

    But these client objects have a contact array ...

    Given this scenario, what is

    a) best practice

    b) Theoretical

    c) practical

    Should I create two types of class for clients and contacts (with / without the variable arrays)

    Julian

  • Could be that you are finding this more puzzling because of your chosen implementation, i.e., internal arrays rather than collections.  With an internal array, there is a tendancy to think of the contents as part of the object.  But, in fact, what you have is a relation between objects, either a relation between a contract and the clients it corresponds to or between a client and the contracts it has.  Collections are merely the plumbing that one uses to express a one to many relationship.  That relationship may or may not be relevant in a particular context and, when it is not relevant, it is not populated.

    So, depending on context you might have one or more contracts (more than one => another collection).  Clients may or may not be of systematic interest.  If they are only sometimes of interest, then instantiate the clients and build the collections as needed.  In a different context, you might have one or more clients (as above).  The contracts may or may not be of systematic interest.  When they are, then instatiate the relevent contracts and populate the collection you need.

    If you run into a situation where you need both contracts and clients extansiated and need to walk the relationships in both directions, then instantiate the needed objects and collections on both sides.  No problem.  E.g., it is perfectly reasonable to walk the relationship from a contract to related clients and then to walk the reverse connections to come up with all of the contracts which affect this group of clients.  Since collections are just set of pointers to objects, no object will get instantiated more than once.  E.g., if a contract points to two clients and the same two clients also have two other contracts, you will end up with three contracts, two clients, one collection of clients, and two collections of contracts.  That actually describes the relationship perfectly.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • >> Could be that you are finding this more puzzling because of your chosen implementation,

    Possibly, but before I read the entire post, I implemented it this way

    so that I could code like

    message Customer1:Contact[1]:Name view-as alert-box.

    Granted, trivial code example, but could I do that with collections ?

    It's also very handy because you can use extent(Customer1:Contact) to

    know how many contacts that client has

    If the relationship is not relevant, could I not simply have two

    methods in the client controller

    Get

    GetWithContacts (which uses the Get method in the contact controller

    (ie no clients))

    and in the contact controller

    Get

    GetWithClients (which uses the Get method in the client controller (ie

    no contacts) )

    Julian

    On 2 January 2011 22:33, Thomas Mercer-Hursh

  • Given variable arrays, you sort of need to know the count of the set before you can create the array ... or incur a lot of overhead expanding it.   You could make the array big enough to handle most cases based on your knowledge of the domain and avoid the expansion problem, but then you couldn't use the extent to find the count.  Given that you have to fetch the members of the set to build the collection, however that collection is implemented, then knowing the size is trivial anyway.

    My main point is to get you thinking in terms of relations ... however you decide to implement the collections.  Collections is the Right OO way to do it, but do what you will.

    Rather than thinking of special methods, I would rather see you do a form of lazy instantiation.  I.e., instantiate just the base object first and then if you do anything that needs to access the collection, notice that it is not yet instantiate and go ahead and instantiate it at that point.  You could do this pretty easily with Count by using ? for unitialized and 0=>N once initialized.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • So what you are trying to batter into my thick little head is that

    within the Client object, I have a Contact property (which is a

    collections object, not a contact object)

    So I can use it like

    Client1:Contact:Item(1):Name (for collections)

    instead of

    Client1:Contact[1]:Name (for variable arrays)

    and then use lazy instantiation on :Contact: (within the getter of

    :Contact:, check if it's a valid collection object, if not create one

    and populate)

    That would work for me.

    Would you recommend a standard collections object for all cases like

    this (casting issues), or a specific collection object for contact,

    client etc (coding issues - mitigated by code generators)?

    Thanks for the help.

    Julian

    On 2 January 2011 23:05, Thomas Mercer-Hursh

  • Would you recommend a standard collections object for all cases like

    this (casting issues), or a specific collection object for contact,

    client etc (coding issues - mitigated by code generators)?

    I personally would prefer the strong typed solution. If your are not yet using a code generator, consider writing the collection or list class using an include file with the target type/class as an include file parameter - as mentioned in the super hijacked thread on Generics. A very practical solution.

  • Inlcudes == eeek

    Code generators would work a lot better for me.

    I must say that I think strong typed makes more sense for me in this situation.

    Thanks

    Julian

  • In normal OO usage, collections are almost always generic, exactly because they are a piece of the infrastructure, something the code generator sticks in to realize a one to many relationship in the UML rather than something that is shown in the UML diagram per se.  There are, like anything, exceptions where on can benefit from a non-generic solution, but one thinks of generic first.  This goes along with reuse, of course.

    No, you are not going to directly address a property in an object in a collection.  At least, not with the usual usage of collections.  Instead, normal practice is to iterate through the members of the collection, retrieving each member to a current object, and then doing whatever operations you need.  For UI cases, it can be sensible to use temp-tables to hold this sort of stuff rather than collections, so I am thinking here more of something like "renew all contracts for this person for an additional year" types of operations.

    In fact ... while I try to avoid thinking about UI as much as possible ... I'm not sure that for UI purposes I wouldn't just go with a PDS containing tables and join tables and forgo the actual objects themselves.  At that point one is trying to provide backing data for a grid or whatever, not do business logic.  Mike will probably faint when he reads this!

    Usually one navigates a collection with something like GetNext() that returns the next member of the collection and throws an  error when there are no more.  Yes, not having a valid handle for a collection object is a perfectly reasonable indicator that one needs to go instantiate and populate it.  Collections do normally have a Size property.

    This http://www.oehive.org/CollectionClassesForOORelationships covers my most recent thinking about collections.  I haven't done the code yet, though.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • Strongly typed means a different collection class for every purpose ... each of which does almost exactly the same thing.  As Mike says, the empirically pratical way to do this is with includes, but I share your disaffection for includes.

    Besides, I question the gain.  The purpose of the collection is just to manage the relationship.  At any one time, you are only going to operate on any one member of that set, e.g., CurrentContract.  So, GetNext into an object of the right type and presto all the methods and properties of that time are immediately available.  No different really than iterating though a temp-table and operating on the current buffer.  This is something that you can do entirely generically and without then having a million type-specific collection classes that all have the same code.  Presto, hundreds more objects and failure to re-use, not to mention having to use includes to get there efficiently.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • Mit freundlichen Grüßen / Kind regards

    Inlcudes == eeek

    Old school - but better than copy and paste.

    Code generators would work a lot better for me.

    "would" sounds a lot like you don't have one yet? Time to get started, then. Or bite the pill with the includes.

  • Besides, I question the gain.  The purpose of the collection is just to manage the relationship.  At any one time, you are only going to operate on any one member of that set, e.g., CurrentContract.  So, GetNext into an object of the right type and presto all the methods and properties of that time are immediately available. 

    Sorry, are you saying a non-typed, non-generic collection would be sufficient? That would require permanent casting and runtime type-checking that both Julian and I want to avoid.

    I'd rather create 1.000 (sure no app would require million's) generated or include file based typed collections.

  • Mike will probably faint when he reads this!

     

  • Whether one uses includes or one uses a code generator to produce what the include would result in, one still ends up with N objects which have entirely the same logic, but different data names.  Not only is this horrible in terms of code reuse, but it implies that one is putting logic in the wrong place.  The only reasons I can think of to want type-specific names inside a collection are either to be able to treat it like an array, i.e., the sort of addressing which Julian was indicating in his code fragment, or to provide type-specific behavior as a property of the collection.

    I see no reason to try to treat the data like an array except for cases when the data is actually an array and then it is containing in one object.  What is the use case in which one wants to go directly to the Nth row/object rather than iterating through the set?  And, even if one does, how is this not satisfied by a generic key/value collection?  One doesn't even have row/column addressing for temp-tables.  One always has to locate the desired row and then access its properties by name.   So should it be for objects in collections.  Locate the desired object (typically the next) and then access the properties and behavior of that object.

    Likewise, type specific behaviors of a collection are also very dubious.  For starters, one then not only has N different objects, but each one of those potentially has type specific behavior.  Uggh.  And, to what advantage?  What kind of type specifc behavior are you going to put in the collection which doesn't instead belong in the parent object?

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • I'm saying that a non-typed *generic* collection would be sufficient.  The collection stores objects as Progress.Lang.Object.  The object using the collection treats them as what ever type they actually are.  If you store an object of type X in a PLO, it doesn't stop being an object of type X.  There is no big conversion that needs to happen.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • The collection stores objects as Progress.Lang.Object. 

    We agree on that.

    The object using the collection treats them as whatever type they actually are.  If you store an object of type X in a PLO, it doesn't stop being an object of type X.  There is no big conversion that needs to happen.

    That's the point that I have a problem with. I see a fundamental different between a List cannot store a Supplier or System.Windows.Forms.Button or Progress.Lang.AppError object. So it makes the interface/contract more robust.

    Regarding the implementation and actual code duplication: When using includes or a code generator, that is the only way I am aware of in the ABL (beside stupid hand coding) that would allow the creation of a type-safe List or Collection etc.. This does not say that I actually duplicate the code that manages the set internaly. That could be done in a base class working on Progress.Lang.Object. The typed List would just handle the CAST and provide the type safe interface.

    I see a lot of benefit and simplification of use in that.

    So my ListOfCustomer would inherit from an abstract GenericList class.

    The method GetItem of ListOfCustomer looks like this:

    PUBLIC METHOD Customer GetItem (I AS INTEGER):

    RETURN CAST (SUPER:GetItemInternal , Customer).

    END METHOD .

    GetItemInternal of the GenericList class returns Progress.Lang.Object.

    Looks much better to me than having the CAST all over the place where I'm using the Collection of List. Plus, I can trust that I get a Customer (or better) when the method returns a Customer. You should not trust anybody (except yourself) returning a Progress.Lang.Object that it returns what you expect.