The Object That Remains
I kept meeting the same idea in different uniforms. COM lets clients ask an object for an interface. Game-object models let code ask for a component. Property systems reveal attributes that the base class has never heard of. The terminology changes, as software terminology tends to do. The mechanisms differ, but the design pressure behind them is remarkably consistent.
Interfaces first made the distinction visible to me. Different clients could encounter the same object through narrower contracts, without any one contract being the whole object. Component systems and external properties later showed that the same distinction applies to behavior and state.
The more fundamental subject was identity: the distinction between the object that persists and the data, behavior, and capabilities associated with it. The familiar definition of an object as “data plus methods” obscures that distinction.
The Classroom Object Is Too Small
Many programmers first meet object-oriented programming through a compact formula, the sort of definition that fits neatly on a lecture slide and soon acquires the authority of natural law:
object = data + methods that operate on that data
As an introduction to encapsulation, this is useful. As a definition of what an object is, it quietly collapses several different ideas:
identity Who or what is this?
state What is currently true about it?
capability What can it do, or what can be done through it?
implementation Where are that state and behavior represented?
In the class-based model familiar from C++, Java, and C#, a class is simultaneously a type, representation, home for operations, and protection boundary. Treating that useful arrangement as the complete truth about an object encourages fat classes and inheritance trees. It can also make delegated behavior seem “anemic” even when delegation expresses the real ownership boundary.
But a class is not necessarily the object. It may be one implementation, one representation, or one place where the object’s contracts are assembled.
OOP has always contained broader models, from simulation participants with lifetimes to objects responding to messages. The narrower problem is identifying objecthood too closely with one class definition.
A more useful distinction can be stated in shorthand:
value object = contents
entity = identity + continuity
A value object is determined by what it contains, not by which instance it is. An entity is an identity-bearing object, and continuity is the traceable history of transformations recognized as valid by the domain. State and behavior can be associated with an entity through contracts rather than physically contained by one class.
Those contracts can be fixed at compile time, remain stable for the life of an instance, or change at runtime. State can come from storage, behavior can be delegated, and physical representation can change. Across those arrangements, continuity rather than containment lets the system recognize the same entity through change.
A coordinate, color, date range, or monetary amount is often a value object, defined completely by its contents. A document, account, device, or other entity needs an identity rule that connects its successive states.
A Well Is More Than Its Current Parts
I use a drilling well because I have worked on software built around wells. A well’s identity can outlast changes to its physical composition, while its names, equipment, observations, operational state, and presentation properties change under different rules.
A UI may show the well with a name and a color, but neither property necessarily belongs to the well in the same way as its physical or operational state. A name may come from an operator or regulatory registry. A color may belong to a visualization, a user preference, or a policy that colors wells by status. Different views can legitimately render the same well differently.
Even the physical meaning of “well” is not simple. Is it the location, the drilled wellbore, the wellhead, the casing, the installed instruments, or the legal asset? Equipment can be installed and removed, pipes replaced, and the wellbore extended as drilling continues. Yet the organization can continue treating it as the same well.
Well identity
-> names and regulatory records
-> one or more wellbores
-> installed equipment
-> drilling and completion operations
-> measurements and observations
-> view-specific appearance
Each association can stay with its natural owner: a name in a registry, equipment in an installation relationship, measurements in an observation store, and color in a visualization. They remain facts about the same well because the system relates them to the same identity under explicit rules.
The well is more than an arbitrary key. Its lifecycle, valid relationships, ownership rules, and history determine what may legitimately be associated with it. Identity provides continuity; domain rules provide coherence.
Where The Class-Centric Model Becomes Too Narrow
The well establishes the design problem: one identity must remain coherent while related facts, capabilities, and representations have different owners and lifecycles. A class can provide a perfectly useful view. Trouble begins when it is asked to become the well, the registry, the equipment ledger, the observation store, and the visualization layer all at once.
The examples span identity levels from a process-local reference to a durable domain entity. Each makes its own guarantees; the shared concern is separating what persists from one representation or view of it.
Not Everything Starts With A Class
Prototype-based languages begin with the most basic separation: an object can exist without a user-defined class. In Self, and later JavaScript, it has its own identity and properties, while delegation supplies other state or behavior through a prototype.
const renderable = {
render() {
console.log(this.name);
}
};
const object = Object.create(renderable);
object.name = "Example";
object.render();
The render function is found through the prototype chain rather than stored directly on object, but the call still uses object as this. Modern JavaScript’s class syntax remains built on this model. Delegation shows that objecthood does not depend on a user-defined class; it does not provide formal conformance or durable domain identity, and mutable shapes can weaken discoverability and invariants.
One Identity Can Have Several Views
Once an object no longer depends on one class, the next bond to examine is the one between identity and interface. COM makes an object navigable through explicit interfaces.
A COM object can expose several interfaces. A client does not need the implementation class; it asks through QueryInterface whether the object supports the contract it needs. The interesting part is the separation:
The same object can be approached through different contracts.
One client sees IShape, another sees IResizable, and another sees IPersistable. None of those interfaces is the whole object.
What interests me here is not the lookup mechanism but the identity guarantee. Querying any supported interface for IUnknown must return the same canonical pointer, which lets a client determine whether two interface pointers belong to the same COM object. Microsoft’s QueryInterface rules also require reflexive, symmetric, and transitive behavior. The result is a navigable family of contracts around one identity, not merely a method lookup convention.
COM’s multiple faces have a firm boundary: the set of interfaces reachable through QueryInterface remains static for a given object instance. If a query succeeds once, it must continue to succeed. COM offers several views, but fixes the set for the life of the object.
Extensions And Components Attach Capabilities To Identity
Several interfaces can still leave the capability set fixed. Extension and component models take a further step by representing a capability as an object of its own and, depending on the model, allowing the set to change.
Erich Gamma’s Extension Object pattern addresses a familiar pressure: different clients need different interfaces, and a base abstraction cannot sensibly predict them all. A plugin can contribute a new extension without adding another method or dependency to the host class. The host provides a way to locate extensions, but it need not understand each extension’s contract or purpose.
An extension’s lifetime may be tied to the host that exposes it. This allows the extension to add a capability to an existing identity without pretending to be an independent domain object. It is a practical application of the Open/Closed Principle: plugins can add capabilities while the host remains unchanged. The price is runtime lookup, less obvious tooling, and lifecycle rules that the extension mechanism must define clearly.
I see a related separation in a distributed system I work on. One microservice defines and persists a well under a synthetic ID. Other services use that same ID to associate their own records and operations with the well, covering drilling fluids, bottom-hole assemblies, blowout preventers, and other domain concerns. The core service does not need to absorb those models or even understand all of them. No single service contains the whole conceptual well; each owns a domain-specific part reached through a shared identifier.
The analogy has a limit. Reusing an ID establishes correlation, not coherence. The services still need rules for whether their records may precede or outlive the core well, what deletion means, and how cross-service invariants are maintained. A shared key gives distributed state somewhere to attach. It does not, by itself, prove that the state belongs there.
Inside a process, game-object components make a similar move: they attach capabilities to a stable game-object identity. Their lifecycle rules differ from Extension Object, but both spare the base class from becoming a catalogue of every possible role.
Calling GetComponent<T>() returns the requested component, or null when the game object has none. Instead of encoding every combination in an inheritance hierarchy, a game object can compose Transform, Renderer, Health, Weapon, AI, and other components. The GameObject acts as a stable handle while an editor or runtime changes the attached behavior and state.
That mutable composition goes beyond COM’s fixed set of interfaces. A game object may be damageable and physical today, then become collectible, networked, or player-controlled tomorrow. The flexibility has costs: required capabilities may be absent, separately developed components may disagree about invariants, and behavior becomes harder to find. Composition avoids an inheritance explosion, but coherence still has to be designed.
A data-oriented Entity-Component System (ECS) takes the separation farther. A Unity-style game-object component can contain state and behavior. In an ECS, the entity supplies identity, components primarily hold data, and systems operate on every entity whose component set matches a query.
That split also permits a different physical layout. An archetype-based ECS groups matching component sets into tightly packed chunks so systems can iterate over only the data they need. In Unity’s archetype and chunk layout, an entity’s data may move when it gains a component or when a chunk is compacted, while its entity ID remains stable. Identity remains while data moves, although structural changes still have a cost.
Attributes Can Live Elsewhere
Capabilities can live outside the base class. Attributes can too.
A property bag lets code ask for an attribute without that attribute being a conventional field or property on the concrete class.
I would still prefer an ordinary member when the shape is stable and the owner is clear. When properties must evolve independently of the concrete class, a serious property system still needs contracts for type, validation, ownership, mutability, and versioning. Otherwise it is merely a string-keyed dictionary with delayed failures.
I have seen properties such as displayColor placed on a domain object because that was the easiest place to reach them. The value may be perfectly typed and validated while still belonging to a particular visualization rather than the well. Type safety does not settle ownership.
That is why “composition over inheritance” is an opening move, not a finished design. Once behavior and state move into components, extensions, or property systems, we still have to decide who owns them, how their invariants interact, and what makes the result one coherent thing.
Identity Is Not An Arbitrary Bag
The harder lesson for me was that separation can go too far. Once state and behavior can live elsewhere, it is tempting to say that nothing really belongs to the entity and split everything into domain-specific parts. That avoids a fat class, but it can leave only a hollow identifier surrounded by disconnected state and behavior.
The answer is not to move everything back into the entity, but to make ownership explicit. Some behavior belongs to the entity’s own rules; some belongs to a policy applied in a particular context. A pressure analysis may operate on well observations without becoming behavior owned by the well, and a visualization may choose its color without changing the well at all.
The same discipline applies to state. An association needs an owner, a lifetime, and invariants. Relevant fields and methods may live elsewhere, but the model still has to define the entity’s continuity, lifecycle, valid relationships, and coordination rules.
That picture is richer than “data plus methods.” The old formula describes a common implementation arrangement. It becomes a poor ontology once proxies, persistence, components, adapters, remote references, and delegated behavior enter the design.
The Ship Of Theseus, With An Engineering Constraint
Identity was the easy word; continuity was the harder part. A stable key tells the system where to attach history, but it does not explain why two states belong to the same history. The Ship of Theseus gives that question its familiar form. If every plank is replaced, is it still the same ship? If every component, property, and implementation behind an object changes, is it still the same object?
Replacing every plank exposes the limit of composition as an identity rule. A vessel repaired over many years may preserve a continuous history, while a replica assembled from its discarded planks may contain more of the original material without inheriting the original identity. What matters is not only which parts remain, but how the later ship is connected to the earlier one.
Software turns the question into an engineering obligation. Continuity is not a component that survives every replacement. It is the traceable relationship between successive states. An object remains the same when its later state follows from its earlier state through transformations recognized by the domain. The system must also carry forward the references, relationships, obligations, and history attached to that identity.
A stable identifier can anchor that chain, but it cannot create continuity by itself. Delete an account and create an unrelated account under the same database key, and the key has survived more convincingly than the account.
Some domains choose a physical anchor, such as a chassis or wellbore, so physical continuity can matter. Even then, no present inventory of parts determines continuity on its own.
Identity is therefore not a hidden substance inside the object’s current representation. It is a relationship between states that the system undertakes to maintain through time.
The ship sharpens the earlier boundary between entities and value objects. A value object needs no chain from one representation to the next; its contents are enough. An entity brings extra obligations: identity rules, aliasing, lifecycle, and history.
Use identity when continuity matters. Use value objects when description is enough.
The Object That Remains
Software needs stable things to reference, relate, own, and reason about. It also needs implementations, behavior, and representation to evolve. The mechanisms in this article make different promises, but they return us to the same design question:
What remains when the data moves, the behavior moves, and the implementation changes?
For a value object, contents are enough. For an entity, a class can provide one representation and assembly boundary, but it cannot exhaust what the object is. What remains is its identity, carried through a traceable history of valid change.
Interfaces were my first clue: the same object could be encountered through more than one contract. Following that clue led me from interfaces to identity, and from identity to the continuity that lets a changing object remain itself.
Sources And Further Reading
- Ole-Johan Dahl, The Birth of Object Orientation: The Simula Languages
- Alan Kay, The Early History of Smalltalk
- Microsoft, Rules for implementing
QueryInterface - Erich Gamma, Extension Object
- Unity,
GameObject.GetComponentand ECS archetypes and chunks - Eric Evans, Domain-Driven Design Reference, and Rich Hickey, Values and Change

RSS - Posts