In the previous article about my C++ event-planning project, I described the emerging object model at a fairly high level: guests, groups, tables, seats, floor-plan elements and the document that eventually brings those concepts together.
Before going further into those business objects, however, I want to move down one level.
The current reconstruction has reached the point where some of the application's foundational classes are becoming clear. These are not necessarily the classes that a user will ever know exist. They do not correspond directly to a screen or a feature. Instead, they provide common concepts on which much of the rest of the model can be built.
One of the most interesting of these is also one of the smallest: a dedicated Object Identifier.
Starting at the bottom of the model
At a simplified level, the emerging architecture has a common Event Object abstraction. More specialised domain objects can then build upon that foundation.
The graphical part of the model introduces another base abstraction for objects that have a physical representation on a floor plan. From there, specialised objects such as tables, walls, shapes, text elements, images and visual keys can add their own behaviour.
The important point is not the number of classes. It is the attempt to put common responsibilities in the correct place.
Object Identifier
│
│ identifies
▼
Event Object
│
├── Guest
├── Group
│
└── Graphical Object
│
├── Table
├── Wall
├── Shape
├── Text
├── Image
└── Visual Key
This is a conceptual diagram rather than a literal C++ inheritance diagram. In particular, the identifier is a value used by objects; it is not their base class.
That distinction matters. Inheritance describes what an object is. Composition describes what an object has. An event object can have an identifier without an identifier somehow becoming an event object.
Why an identifier deserves its own type
At first glance, an object identifier could simply be a string.
Technically, the recovered implementation does store the identifier as textual data. But the design wraps that value in its own small class rather than passing arbitrary strings around the application.
I think that distinction is worth preserving.
A normal string might mean a guest's surname, a table name, a note, an address, a filename or hundreds of other things. An Object Identifier has a much narrower semantic meaning: it represents the identity of an application object.
Giving that concept its own type creates a boundary around it. The class can control how identifiers are created, copied, compared, tested, converted to text and hashed without requiring every consumer of an identifier to understand its internal representation.
What the Object Identifier actually contains
The recovered class is surprisingly compact.
Its persistent state consists of a single field, which I will simply call the identifier text. It is stored using Qt's string type.
That one field carries the textual representation of the identifier. Around it, the class provides the operations needed to treat that text as an identity rather than as arbitrary user-entered content.
Conceptually, the class looks like this:
Object Identifier │ ├── Identifier text │ ├── Create a new identifier ├── Construct from existing text ├── Copy an identifier ├── Compare identifiers ├── Test whether it is empty ├── Obtain its textual representation └── Produce a hash value
This is a good example of a class whose importance is not proportional to its size.
There is very little state, but the abstraction can potentially be used throughout the object model.
Existing identities can be reconstructed
The class is not limited to generating new identities.
It can also be constructed from an existing textual identifier. The recovered implementation normalises the supplied value by removing surrounding whitespace. If meaningful text remains, that value becomes the identifier. If the supplied value is empty after normalisation, the class can generate a new identifier instead.
This behaviour is particularly relevant to persistence.
When an existing event is loaded, the application must be able to reconstruct the identity that an object already had. Generating a different identity every time an event is opened would defeat much of the purpose of persistent identifiers.
The same abstraction can therefore support two very different situations:
New object
│
└── generate identity
Loaded object
│
└── restore existing identity
That is a small design decision with significant consequences once objects begin referring to one another.
Identity is different from a name
This becomes clearer when considering a guest or a table.
A guest may have a name, but the name should not normally be the object's identity. Two guests can have exactly the same name. A guest's name can also be corrected without turning that person into a different object.
A table has the same problem. The user might rename “Table 4” to “Family Table”. The presentation has changed, but logically it is still the same table with the same relationships to seats and guests.
The identifier provides an independent identity that can remain stable while user-facing properties change.
The identifier is generated, not merely numbered
The recovered implementation also provides some insight into how new identifiers are produced.
They are not represented as a simple sequence such as 1, 2, 3 and 4. The generation algorithm constructs a textual identifier from multiple pieces of information. A time component is part of that process, represented in hexadecimal form and padded to a fixed width. The implementation also interacts with application-level user state as part of constructing a new value.
I am deliberately not reproducing the exact internal format here. The relevant architectural point is that the identifier is designed to contain substantially more distinguishing information than a small document-local integer.
Conceptually:
Time information ───────┐
│
Application/user state ─┼──► Identifier generation ──► Object identity
│
Additional sequencing ──┘
At this stage of the reconstruction, I am more interested in understanding the semantics and guarantees of this mechanism than blindly preserving every implementation detail. The format is part of the recovered behaviour; whether every detail belongs unchanged in the modern implementation is a separate engineering decision.
Comparison and hashing matter too
The class also supports direct comparison between identifiers and can calculate a hash value.
Those operations are exactly what I would expect from an identity value that may eventually be used heavily in collections and relationships.
Instead of repeatedly comparing several properties of two objects to decide whether they represent the same logical entity, code can compare their identities. Hash support also makes the identifier suitable for hash-based lookup structures where appropriate.
This starts to become valuable as the model grows:
Guest identity ─────────────┐
│
Group identity ─────────────┤
├── relationships
Table identity ─────────────┤
│
Graphical-object identity ──┘
The diagram is conceptual: not every relationship in the application necessarily stores identifiers in precisely this manner. What matters is the role stable identity can play as objects become interconnected.
Then comes the common Event Object
Immediately above these small infrastructure concepts, the architecture introduces the common Event Object abstraction.
This is where the model starts to acquire meaning specific to the application. Guests and groups belong to this family, as do the objects that eventually participate in the graphical floor plan through a more specialised Graphical Object base.
The progression is therefore becoming clearer to me:
Identity
│
▼
Common event behaviour
│
├── People and groups
│
└── Graphical behaviour
│
└── Physical floor-plan objects
This is the part of object-oriented design I find more useful than simply drawing large UML diagrams.
The question is not “How many base classes can I create?” It is “Which responsibilities are genuinely common, and at what level do they become common?”
Identity is very low-level. It does not need to know whether it belongs to a guest or a wall. The common Event Object can then deal with behaviour shared by application objects. The Graphical Object can add concepts that only make sense for something that appears on the plan. Finally, concrete objects such as a table or wall can implement their specialised behaviour.
Recover first, modernise deliberately
There is another reason I am spending time documenting classes this small.
This project is not purely greenfield development. Part of the work is understanding an existing compiled application's behaviour, reconstructing its design and then deciding how that behaviour should be represented in modern C++ and Qt.
Those are three different activities.
First I need to establish what the original software actually did. Then I need to understand why the classes and relationships may have been designed that way. Only after that should I decide which parts deserve to survive unchanged and which should be modernised.
The Object Identifier is a good example. I could replace it immediately with some standard identifier mechanism and move on. But doing that before understanding its generation, comparison, persistence and relationship semantics would throw away information that may become important elsewhere in the application.
So, for now, the objective is fidelity first and redesign second.
A small class, but an important foundation
The Object Identifier will never be one of the glamorous parts of an event-planning application.
No user will buy the software because its internal objects have well-defined identities.
But once guests belong to groups, guests occupy seats, seats belong to tables, tables appear on floor plans and all of those objects must survive saving and loading, identity stops being an implementation detail.
It becomes infrastructure.
That is why, at this stage, I am deliberately working from the bottom upwards. Before discussing sophisticated seating optimisation or polished graphical interfaces, I want to understand and reconstruct the small classes that make the rest of the model reliable.
Sometimes the most important architectural work begins with a class containing only one field.
Where relevant, factual and regulatory references were checked against the sources cited in the article. AI assistance does not replace professional legal, regulatory, financial or technical advice, and the final selection, interpretation, opinions and conclusions presented here remain my own.
Comments
Post a Comment