Seebs wrote:
[...]
I don't think so. Even if john and mary both have the same base
strength,
and the same modifiers, they're not the same thing -- making john
stronger
doesn't make mary stronger.
I didn't say the same object -- I said the same *thing*.
a = Stat.new(15, -2)
b = Stat.new(15, -2)
It doesn't matter in the slightest whether a.object_id == b.object_id.
Regardless, a and b are *the same thing* to all intents and purposes --
and you can tell that, in part, precisely because the object_id is
irrelevant. John's strength isn't the same thing as Mary's strength,
but that's a very different issue. The English concept "John's strength
(in the abstract)" is equivalent to the Ruby method call john.strength.
The English concept "the concrete value that, for the moment, represents
John's strength" is equivalent to the return value from that method
call. You're confusing the method with its return value. The
difference is subtle but important.
English: John's strength, in the abstract, isn't the same thing as
Mary's strength in the abstract.
Ruby: john.method
strength) != mary.method
strength)
English: The current value of John's strength happens to be equal to the
current value of Mary's strength.
Ruby: john.strength == mary.strength
Does this make sense?
Right -- which is why my initial plan was to commit that, after
initialize(),
john.strength DOES always yield the same object, no matter what changes
are made to its value.
Why? There's no point to doing so as far as I can see. Even if it were
a Fixnum, it would already be yielding different objects every time it
changed. The client should make no assumptions about
john.strength.object_id.
This sounds extremely expensive for an extremely commonplace thing. In
an ordinary turn of a game, I might have thirty or forty modifiers which
change, meaning that I'd be duplicating-but-modifying non-trivial object
trees thirty or forty times.
It may sound expensive, but it really is the best way of handling value
objects. This is how classes like Fixnum, Date, and BigDecimal work.
(Actually, Fixnum uses the Flyweight pattern under the hood; the others
might also.)
Yes, I know that.
But I don't think a stat is a value. I think it's a complicated model,
which
happens to yield a value.
History of previous values (in some cases), and the modifiers aren't
fixnums; they're name/value pairs
Really? You're not just doing
@modifiers = {:racial => 2, :class => 1, :armor => -1}?
probably plus other traits such as
a duration (some expire, some don't).
Does that really belong in the values itself? I'd tend to think not;
rather, that probably belongs in your event system.
There will probably be more
tweaks to that over time.
OK, then this is getting more complex than a simple value object. If
you want history, maybe you should have a separate StatValue object, or
simply a hash.
But if you want history, then the StatValue object or whatever you wind
up using will probably have to be immutable anyway, so you can be sure
your history is accurate!
As time goes on, stats are expected to be much more complicated.
Then refactor your design at that time. Design for what you have now,
rather than predicting the future. Remember YAGNI.
A stat
will likely have its original starting base value, its highest recorded
base
value, and a set of modifiers (which may change quite often). Possibly
more
things beyond those.
Since you don't know yet, don't design yet for them (your future plans
could easily change). Design a model that works for what you actually
have, and be ready to change it as necessary.
So right now, I think it is probably nearly-sanest for me to have it
delegate
nearly everything to the fixnum current value, which really is a value
object.
So it isn't a value, but it delegates to one.
Basically, anything that has internal state to keep track of which is
not part
of how you use it strikes me as not really being a value, even if it
looks
like one from the outside.
Largely correct, although I can think of exceptions. You'll probably
want to break this down into a composed object containing one or more
value objects.
That said!
I am thinking that perhaps it should *present* as immutable -- that is,
I
should not define john.strength = 15 in any way, but rather, if you want
to
ask it to change its value, you have to send it messages. So instead of
john.strength += 1, you'd write john.strength.adjust_by(1).
No need. The former is a lot friendlier. You can always redefine
Character#strength= .
Hmm. I suppose the thing, then, would be to simply not provide a
strength=
method for the things that have stats.
That strikes me as silly. There's no reason that the client class
should care whether your strength= method *really* does an assignment
operation.
Remember, one of the nice things about OO programming is that interface
and implementation needn't resemble each other in the slightest.
Best,