Thursday, July 22, 2010

Group Objects

Let us define a group object as a collection of the objects accessible through the common instance reference. The member objects may or may not have any relations with each other. They may or may not have intersecting message mapping. The content of the group can be changed dynamically during the object life time (self-modification). The member objects can be a part of different groups or be single. They may not even notice they become a part of the group (dynamic overriding). The group can be persistent or temporally created to solve the particular task. In general the group objects are treated like "normal" ones and no special routines are required to work with them. If two or more members have duplicate methods either the first message mapping is resolved (exclusive mode) or all duplicate methods are executed (broadcast mode) depending on the group type.
So how may group objects be used? Let us consider several possible use cases: code customization, "branching-free" / self-modifying algorithms and horizontal inheritance.

Customizing / Extending existing code

The concept of the group object may be used for modifying the existing code. Sometimes only minor changes should be applied to the code to be reused. Commonly we may override the class. Or we could combine it with the special adapter class containing only the methods we would like to change (actually dynamically override it, see horizontal inheritance). Though this two patterns are quite equivalent, the group can be formed with any object implementing a particular protocol at a run time. And in contrast to an aggregation we have to implement only the methods we would like to change without affecting others (which is quite important for the dynamic language where we do not always know the object type). Moreover in some cases we will not need to write a new code at all, only combining well-defined components. In general it allows us to inject the code into the complex system without big modifications and this injection may be invisible for other parts of it.

"Branching-free" algorithms

The content of the object group may be changed during its life time and it gives us quite an interesting feature: every time the group internal state is changed its actual content may be changed as well. Though the total number of the different states is quite big we can always select several extreme ones (empty list, unassigned reference, read-only collection and so on), place the code related to them into a separate control object (or several ones) and combine it with the main code. So when the group state is changed the control object replaces itself with the appropriate one instead of checking the state every time (alternatively the control object may have a several set of VMTs). Self modifying group can be used as well in many cycle algorithms where the first or the last action are different from the main ones. The real "branch free" code is unlikely to be feasible but the moderate usage can still simplify the code.

Horizontal inheritance

Group object can be used as an alternative to a multiple inheritance as well. Common satellite classes can be included in different groups allowing them to share the code without actually inheriting it, helping us to reduce the depth of inheritance tree.

Having looked at several use cases let us examine the possible implementation of the group object concept.

Possible implementation

Being born as a part of the dynamic language study the group object definitely requires a pure polymorphic language with a late binding. For implementation simplicity the concept of limited object interface was introduced. We presume that any class method may have only one input and one output parameters. If the method does not require any parameter a special void parameter is passed (e.g. nil constant). If the method does not require to return anything it returns itself (e.g. self variable). As a result we have a system with relatively big number of "simple" classes actively interacting with each other to resolve any task (so we can say that the number of inter-class calls tends to be higher than the number of inner-class ones). Finally let us introduce a proxy handler concept. The proxy handler is a special kind of a method which is executed every time there is no message mapping in the class VMT. It may redirect the call to the specific target.
Summering all above any group object consists of the array of group members and a VMT containing only a special proxy handler. Depending on the type of the group it redirects the call to the first object handling the appropriate message (exclusive mode) or to all members (broadcast mode). Every time a member method is called the reference to the group is passed to it rather than to the object itself. So when the method calls another object method it is in fact redirected to the group (dynamic overriding).
The only problem with this approach (except performance of course) is an access to the object fields. To solve it we need to keep the reference to the object as well. In a standalone mode both variables refer to the object. In fact having two object references allows the programmer to decide whether the method call might be overridden or not.

Name conflicts

But how predictable could such groups be? After all we combine the objects which are not always well known to us. Will there be any message conflict leading to unexpected results? To ease this problem we may introduce the concept of the message namespace. The words in a human language are quite ambiguous and as a result the message names may be ambiguous too. "AND" could be bitwise or Boolean operation, "GET" may return any value and so on. So let us introduce a special language entity of a message scope which describes well-defined term (e.g. "integer", "boolean") and list possible operations with it (e.g. "get", "and" ,...). Any message used by the group member should belong to one or another message scope (which has to exist physically). As a result the chance that the different members of the group will use occasionally similar messages is quite small.

Critical review

Though the scheme above seem a bit complex it costs not so much: an extra check per message call and an additional object variable (if we compare it with the cost of transition from a static call to a dynamic one). But in any case we are facing a bigger concern - redundancy. It is easy to show that most of the possible features could be implemented without groups (after all any high level programming language is translated to the machine codes and by definition cannot be more powerful than the assembler).

So is the introduction of the new entity really justified? And though there is no simple answer to this question several reasons can be mentioned. First of all it promotes more polymorphic and extendable code. The possibility to customize the object behavior without actually knowing much about it (except supporting protocols) will give a programmer more flexible way to deal with existing / reusable code. Secondly this approach reshapes the system structure. Instead of multifunctional classes we will get plenty of rather simple ones with limited functionality which can be easily tested and reused. And finally combining them into super entities makes the system more open and configurable.
To summarize this type of redundancy may be a key factor how to build and maintain more powerful and complex systems with an open architecture. And though it is too early to say if the concept of the group object makes the difference it can give a boost to active use of more structural self-modifying collaborative code.

No comments:

Post a Comment