Seebs wrote:
> On 2009-11-17, Marnen Laibow-Koser <marnen / marnen.org> wrote:
>> 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.
> 
> Perhaps, but (15, -2) and (14, -1) are also *equal*, even though they're
> obviously different things.
> 
> Two fives equal a ten.  When I am talking about money, all I care about
> is the net value, not the internal representation (coins or bills). 
> Unless
> I have to deal with a vending machine.
> 
> But 99% of the time, 10==10 is the correct interpretation of a 
> comparison
> between two fives and a ten.

a = Cash.new(Array.new(2, Banknote.new(5, :usd)))
b = Cash.new([Banknote.new(10, :usd))
a.to_money = b.to_money
a.banknotes != b.banknotes
a ?== b # depends on your definition of equality.

> 
>> call.  You're confusing the method with its return value.  The 
>> difference is subtle but important.
> 
> Hmm.
> 
> Oh, heyyyyy.
> 
> Okay, so one option would be:
>   def str
>     @str.to_i
>   end
> 
> In short, just return the value rather than the object I'm using to 
> model
> it.

That would be the appropriate solution if you never wanted to expose the 
Stat object.

> 
> The problem is, I sort of want to be able to do things like:
>   john.str.add_modifier("drunk", -3)
> 
> Because really, the fact that str is a stat *is* part of the intended
> published interface.

Then your clients will know that it's a Stat object, and expect to call 
to_i on it.  I don't see anything wrong with that; you can overload Stat 
+ Fixnum and so on as we discussed earlier in this thread.

But if you really want automatic, you can have it:
class Stat
  def to_int
    self.to_i
  end
end

(According to http://www.rubyfleebie.com/to_i-vs-to_int/ , to_int is 
called automatically as necessary and will make your class act as an 
Integer.  to_i, of course, will not do that.)

[...]
>> 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.
> 
> Not as a specified interface that people should rely on, but rather, as 
> an
> implementation choice -- it seems crazy to me to regenerate whole 
> object-trees
> frequently.

For your use case, you're probably right.  For real value objects, it's 
a different story.

> 
>>> 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.)
> 
> None of those classes are complicated trees containing five or ten or
> thirty other objects, though, are they?

Fixnum probably isn't.  Date probably contains about 5 other objects 
(totally guessing here -- haven't looked at the implementation).  The 
BigDecimal library I wrote for Rubinius contained about 3-5 other 
objects, but I don't know if MRI or JRuby does it the same way.  The 
Address class I wrote for Quorum ( http://quorum2.sourceforge.net ) 
contains about 6 fields in an immutable value object.  Of course, I 
rarely only need to modify one field in someone's address.

> 
>>> 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}?
> 
> Modifier is a class too, which can handle things like counting down its
> duration, etcetera.
> 
> And a stat has a handful of them.

OK...*these* are your value object candidates, perhaps.

> 
>>> 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.
> 
> It seems to me that the knowledge that a modifier expires is best 
> handled
> by embedding it in the modifier.

It might be.

> 
>> 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!
> 
> Ahh, but I don't necessarily care whether it's accurate, just whether I
> have a record.

If it's not accurate, then there's no point keeping a record.

> 
> For a concrete example:  A lot of games would have, say, a strength 
> stat,
> and things that can lower your strength.  Then you find a "potion of
> restore strength", which restores your strength to its highest previous
> value.
> 
> To my mind, this is the kind of thing that should be handled entirely
> by the stat object.  It's not john's job to know what his highest score
> was for a given stat; it's the stat's job to know what its highest score
> was.

That depends.  A good argument could be made for having the Character 
store its own history, and just having the Stats be values.

In either case, though, for this sort of functionality you don't 
necessarily need a full-fledged history.  All you need is something like
class Stat
  def value=(new_value)
    @max_value = [new_value, @value].max
    @value = new value
  end
end
If you're ambitious, you could put together something like 
ActiveRecord's before_save (which is probably what I'd use for this in a 
Rails app).

> 
>> Then refactor your design at that time.  Design for what you have now, 
>> rather than predicting the future.  Remember YAGNI.
> 
> In this case, though, I really do know that I need a fair bit of this.
> The ability to damage and restore stats is pretty much essential to a
> roguelike.

Well, then you'll run into the design issues when you're ready for them. 
For now, though, and at every future step, try to 
http://c2.com/cgi/wiki?DoTheSimplestThingThatCouldPossiblyWork -- that 
is, work with the state of the app *at that time*.  I know premature 
generalization is tempting, but it's not usually a good idea.

> 
>> 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.
> 
> Yeah, but changing this into an immutable object is a lot MORE work.  So
> I'm not sure I should put extra work into making the object less 
> flexible
> on the off chance that I won't later not need to have done that work.

Right.  It's now clear that your stat object is too complex to be a 
simple immutable value object, although some of its components might 
well be.

> 
>> 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.
> 
> I think so.
> 
>> No need.  The former is a lot friendlier.  You can always redefine 
>> Character#strength= .
> 
> True.
> 
>> That strikes me as silly.  There's no reason that the client class 
>> should care whether your strength= method *really* does an assignment 
>> operation.
> 
> True.
> 
> Hmm.
> 
> So yeah, I think at this point, I really do want a complicated composite
> object, which happens to delegate to a value object which has traits 
> which
> are useful for my purposes -- in this case, almost certainly a fixnum.

I don't think delegation will work here -- for one thing, you may not 
need to store the total value in any actual instance variable.  Using 
to_int or possibly coerce will work much better.

> 
> -s

Best,
--
Marnen Laibow-Koser
http://www.marnen.org
marnen / marnen.org
-- 
Posted via http://www.ruby-forum.com/.