From: Marnen Laibow-Koser Date: 2009-11-17T12:45:13+09:00 Subject: Re: Newbie question: Defining a numeric type Seebs wrote: [...] >> Right. But two stats which have the same base and modifiers *are* the >> same thing, aren't they? > > 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? > >> That's not the way Ruby references work. You can obtain a reference to >> the object returned by john.strength at a given time, but you can't know >> whether john.strength will always return that same object. > > 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. > >>> So when the modifiers change, your solution would be to replace the >>> entire >>> object, duplicating its whole set of modifiers and other state? > >> Yes. That is the best way of handling value objects -- in fact, it's >> the way Ruby handles Fixnums! > > 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.) > >> Object 1 is the Fixnum 1, and object 13 is the Fixnum 6. When the value >> of a changes, the variable a holds an entirely different object >> reference. > > 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. > >> Then maybe a statistic should be composed of several value objects. I >> was operating under the assumption that it's just a base Fixnum and a >> Hash containing a few modifier Fixnums. What else is there? > > 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! > >> From what you've described, Stat isn't large and complicated. What's >> missing from your description? > > 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. > -s Best, -- Marnen Laibow-Koser http://www.marnen.org marnen@marnen.org -- Posted via http://www.ruby-forum.com/.