Composite oriented programming (COP) addresses one of the short comings of OO: That the meaning of an object depends on the context.
In most OO languages today, it’s hard to change the type of an object. Actually, Object Oriented Programming should be called Class Oriented Programming because the class (the type) is the core feature which we use to build application.
But in the real world, objects can have several types. This isn’t polymorphism. It means that COP allows you use the data in an instance within the context of several classes.
For example, in Java, we have two methods which should be context sensitive: toString() and equals(). When I call toString(), I want a certain conversion but that can change depending on in which context I call it. In a debugger, I might want all fields. In the debug log, I might just want the name. In the UI, I will want a nice, user-configurable conversion.
equals() is a similar beast. Different contexts need different equals() methods that work on the same instance. For example in Hibernate, equals() should use the business key. Which is stupid: As soon as a primary key is assigned to the instance, it would be much faster and precise to use that to check for equality. But if you write a tool to compare graphs, your needs will be vastly different.
DI and IoC tools try to fill the similar gaps: At a certain point in time, your code needs a service and you can’t know ahead of time which implementation will be executing the service. While that works somewhat, it’s just a workaround for a fundamental flaw in todays OO languages: Type rules, instances are part of the problem.
Qi4j tries to solve this fundamental problem. Instead of writing huge classes that do everything, the application is created from small building blocks.
The typical case is names. A lot of instances have a name. Instead of writing this code once and using it everywhere, 10 lines of code are copied into every class: The field definition, getter and setter. And maybe you want to set the name from the constructor, so you need 8 more lines (one default constructor and one with the name parameter).
In Qi4j, you define this once. After that, you just add “extends NamedObject” in all the places where you need it. Almost no duplication. Since NamedObject is just an interface, you can collect several of these building blocks in your entity.
I disagree that equals() is a good example of context sensitive behavior, as it effectively defines a contract based on its meta-type (Value, Entity, Service, ‘other’) and Qi4j handles this under the hood automatically.
toString() is perhaps slightly better, as one could simply apply a ToStringDebugConcern if the Application Mode is not ‘production’, for instance.
Better examples would be things like “Assignments”, where a Task can be AssignTo a Project or a Person and a Person can be AssignedTo a Project or as an assistant of another Person. All at the same time.
But thanks for the post.
equals() should be context-independent but if you use Hibernate and a UI framework, you often want two implementations.
Please elaborate. Why does an entity have more than one equals() implementation (Identity)??
In Hibernate, equals() must use the “business key”. For a user, that might be the name. So I can’t use the ID (can be null) or anything else in equals()/hashCode().
In a different context, I might want to use the ID to govern identity because I know for sure that it’s not null at that time. But since the Hibernate rules “leak” into my implementation, I can’t do that.
Unrelated to Hibernate: For special applications, I might want different implementations for hashCode() to optimize distribution in HashMaps. Since equals() and hashCode() are connected, I need to replace both.
Conclusion: Most Java developers are used to the limitations of having just one global identity per object. They don’t feel the pain because they have given up thinking about the problem. But there are use cases where a context dependent identity would really be useful.
Serialization, for example, when you must have a way to identify objects that you already processed. Most of these frameworks define an IdentityMap which uses == instead of equals() but it would be much more simple/clean to override equals() in this context and use the default HashMap().
I think you are incorrect.
Entities are things with a lifecycle, and equals() are simply, “Identity” (primary key in DB, if you talk that language) for Entities and “state equality” for Values.
Examples; “Is this you?”, you don’t go off and check “blue eyes, blond hair, gray coat, and a smile on the face” (the state), there is really only one of you, no matter the context, no matter how your state changes over time.
On the other hand, in most contexts “Money” is a Value, so “Is this $100?” is all about equality on the internal value (this case “$” and “100”).
IF “Money” is an entity in some domain, it then should have an Identity of “Which $100”. Qi4j provide different implementations for Entity vs Value, and that is how it should be, even if it is the same Java interface for both “Money” types.
Your example shows (again) that Hibernate is lacking the differentiation of Values vs Entities, which is essential to handle these things easily.
I can’t fix Hibernate to use a better identity mechanism so what do you suggest?
🙂 It is open source, anyone can fix it… 😉
Yes, Hibernate is a classic example of starting out with a naive implementation first and building on it, i.e. digging an ever deeper hole. And at the end everything is special case and work-arounds.
But since you wrote about Qi4j, and my original “complaint” was that equals() isn’t a suitable example… Qi4j composites can not be handled by Hibernate, although many people keep insisting that we should fix that.
Just to make sure I understand correctly: I could use Qi4j to make equals() context aware but it doesn’t work with Hibernate?
That is correct.
Hibernate has no idea how to instantiate a Qi4j Composite, nor does it understand the Architectural Enforcement (layers and modules), Unit Of Work, Type lookup with visibility, Property model of Qi4j (no getters) and other concepts needed to get this right.
Nor does Qi4j have a Entity Store extension for Hibernate, since the flexible type structures possible in Qi4j is very hard to implement with flexible table structures (that Hibernate tries to solve), and once we go with the key-value approach, where the value is a JSON object, Hibernate has no additional benefit over direct SQL.