"Transforming" a lemming into a new class seems "unnatural" to me.
Yes, I agree that the lemmings should not be replaced. Many of their fields are persistent between jobs. Objects should model the real world: if we don't think of lemmings to be replaced, we shouldn't replace them. My proposed solution is to
replace the behavior object instead.
This solution is compelling in hindsight. I have described its advantages in parts 2, 3, 4. But a
class hierarchy is costly, even though it's idiomatic in OO design. To justify that cost, I will compare alternatives. It's a difficult design problem, and personal preference plays a role, too.
OO with Simon, part 6
Comparison of all-in-base-class, tagged union, and state patternBloated single class: You put special fields for builder, special fields for faller, ..., all into the Lemming class. They're right next to job-independent fields like position or exploder timer. All fields are mutable, and all jobs can access everything.
Widespread access to mutable fields is a concern. Whenever you have a bug, you must inspect lots of code for how it affects the memory. The compiler cannot help.
Dispatching from the monolithic class via manual function pointers (end of part 5) doesn't solve the problem of widely-visible mutable fields.
Tagged union: I assume this is meant by the Delphi cased record trick. You reserve enough memory to hold the job-specific fields for any single job, and put this as a
value field (struct, record) into the Lemming class.
Even though the builder variables now sit in the same memory location as the faller variables, you get to name them per job as
bricksLeft and
pixelsFallen.
This even more type-unsafe than the bloated base class. You can access the builder's fields even when you're a faller. But because they're in a union, not in separate locations, you overwrite the builder's fields when you write to the faller's fields.
A feeble benefit might be speed while making a savestate. You can
memcpy the Lemming to save it entirely, and copy fewer bytes in total than with the bloated base.
State pattern: This is my solution explained in part 2. We make an abstract class for the behavior, subclass it many times, then have the lemming carry a dynamically allocated object for the behavior.
A lemming can have only one state at a time. It's not possible to access wrong fields, and the compiler checks that for you.
Old state objects don't cause long-term psychologic damage to the lemming when he gets a new state object of the same type.
Sometimes, you must access fields of different state classes nonetheless, especially during transistion between states. You can use an explicit dynamic cast -- which angers the object-oriented deities --, or write lots of specialized methods. And herein lie the problems of the state pattern.
About that, I would love to ask someone stronger than me at design for ideas.
NL might not get additional skills. In that case,
don't trade a debugged design for undebugged theoretical soundness. The point of a design to minimize the cost of changing and maintaining it. If you don't want to change much, keep the existing thing.
-- Simon