19 Mar, 2009, JohnnyStarr wrote in the 1st comment:
Votes: 0
I'm having a hard time deciding how to have my objects communicate to each other.
In previous posts some of you pointed out it might be a matter of 'preference' others
felt it was a matter of OO design. let me show you what i mean:

1. user types command -> argument is sent to singleton method for the appropriate command type IE do_say
2. singleton method has 2 parameters (player, argument) their values are obvious. the method then
figures out what to do. in this case, do_say does an act method like:

player.act("%s says: %s", player.name, arg, :to_room)

Ok, thanks for walking through this example. Its contrived but i think you get what i'm doing here. The above code makes
allot of sense to me. it makes more sense that the Player class would have the act method. but there are certain commands
that i'm not so sure about. Consider a command like do_get:

would it make sense to have the Player class have a get_obj method inside of it?

player.get_obj(obj)

or would it be best to keep that in the singleton method do_get which we would pass player as a parameter

Cmd_obj.do_get(player, argument)

So my QUESTION is: Where do i draw the line? Is it a best practice to put as much handeling into classes as possible?
as in, if its something the Player does put it in the player class? I mean, in relation to the other Objects of the mud (rooms, mobs, items etc) should i have a method within the Player class that moves the player from one room to another EG player.move_room(room) or should it that remain in the command method Cmd_move.do_move(player, argument)
19 Mar, 2009, David Haley wrote in the 2nd comment:
Votes: 0
The player object should contain methods for manipulating the player's state. Getting an object isn't manipulating the state, it's some external action that causes a state change in the player. The state change is that an item is added to the inventory: how that happened is kind of irrelevant as far as the player object is concerned.

EDIT: to elaborate: the point isn't necessarily to make your player object be some kind of in-code, self-contained representation of some conceptual player, with anything that player might do as a method in the code. The object is just a tool, and isn't supposed to mirror your conception of how the world works.
19 Mar, 2009, JohnnyStarr wrote in the 3rd comment:
Votes: 0
wow david, very informative!

but i fear it draws out another question, when you say 'state'
how far do i go with that? the state of the player dosn't change when he
gets an item? or leaves a room? i guess not, maybe you could say the state
of the player's inventory changes but not the player itself?

using this example say that we use the players weight attribute. say we do
a get_obj method that gets a bronse sword that weighs 10 pounds and the player's
limit is currently at 11. with your logic we would have the players 'inventory' handled
in an external method. BUT -> handle the players weight check within the player class
due to the fact that the 'state' of that player would be affected?

EG:

player.add_weight(obj.weight)
19 Mar, 2009, shasarak wrote in the 4th comment:
Votes: 0
staryavsky said:
So my QUESTION is: Where do i draw the line? Is it a best practice to put as much handeling into classes as possible?
as in, if its something the Player does put it in the player class? I mean, in relation to the other Objects of the mud (rooms, mobs, items etc) should i have a method within the Player class that moves the player from one room to another EG player.move_room(room) or should it that remain in the command method Cmd_move.do_move(player, argument)

There are all sorts of possibilities. :smile:

You may have a class or method which is responsible for processing a "get" command; but that doesn't mean that the same class or method actually knows how to move an object from one place to another. After all, it's quite likely that you will sometimes want to move an object from one place to another without a player invoking a "get" command. But it's also unlikely that this code would reside in the player class, as objects move around for reasons other than players picking them up. So the actual moving could well be handled by a separate class (let's call it ObjectMover). The get command would create an instance of that class, and set its properties to point to the player, the object being moved and (perhaps) the object's original location.

The ObjectMover instance might then do some processing like this:

1) Test to see if the object is something which can, at least some of the time, be picked up. (In other words, the player is not trying to pick up a room, or a mountain).
2) Check to see that there isn't some temporary condition which means the object can't be lifted (e.g. it is glued to the floor).
3) Check to see if the object's current location is willing to let it go (for example, it isn't locked inside a transparent safe where the player can see it, but not touch it).
4) Check to see if the player is strong enough to lift it.
5) If all of the above executes without any problems, trigger a pickedUp event on the object.
6) If that doesn't cause anything bad to happen (e.g. the object isn't booby-trapped to explode when lifted) check to see if the player's inventory can accommodate an object of that size and weight.
7) If it can, remove the object from its original location and trigger an event representing that.
8) If we're still okay (e.g. the room didn't blow up as a result of an alarm going off) add the object to the player's inventory and trigger an event for that.

Now, most of these steps will involve the object invoking some code in a different object, because most of these steps involve checks that the ObjectMover doesn't (and shouldn't) know how to do. For example, in step 4, the ObjectMover definitely shouldn't query the player's strength score and then compare that with the weight of the object. Instead, it should ask the Player object "can you lift this thing?" - and it should pass the thing itself into that method, not just the object's weight: if it just passes in the weight then that means the ObjectMover is deciding how the Player should do the calculation (that it cannot depend on anything other than weight) and it should be the Player object that decides that.

There's also going to be a lot of inheritance going on. For example, both Room and Player might perhaps ultimately inherit from a class called Container, which represents all objects capable of containing other objects inside them.

Various other things may happen as a result of all the events being fired off. For example, some other object in the player's inventory may melt as a result of being exposed to the heat of the object the player just picked up, or it might burn a hole in the player's pockets and cause other objects to fall to the floor.

It's also worth saying that at plenty of points along the line, a test may fail, or an exception may be triggered. For example, if picking up the object causes it to explode then obviously you don't carry on processing regardless and add it to the player's inventory - it's been destroyed! So that means that the ObjectMover has to be able to trap exceptions, and record the fact that an exception has happened. The get command would then give a message back to the player, with the text of the message depending on whether or not the getting process completed successfully.

The chances are that moving a player from one room to another will use a lot of the same code as moving an object from a room to a player's inventory - but not all the same code because you don't (for example) have to check that the room is strong enough to lift the player. So, in fact, we probably won't just have a single ObjectMover class, we'll have a subclass of that which deals specifically with living things (players and mobs) picking up smaller objects, and inherits stuff from a more generic ObjectMover superclass.
19 Mar, 2009, David Haley wrote in the 5th comment:
Votes: 0
Quote
but i fear it draws out another question, when you say 'state'
how far do i go with that? the state of the player dosn't change when he
gets an item? or leaves a room? i guess not, maybe you could say the state
of the player's inventory changes but not the player itself?

I might have misspoken a little. Yes, adding an object or changing rooms is a state change. But the player is (usually) agnostic to how/why the state changes; the player just gets its state changed. So it doesn't care if the object was added due to a command being executed, due to somebody slipping it into the inventory bag, or due to divine intervention causing the object to appear out of the sky.

Quote
using this example say that we use the players weight attribute. say we do
a get_obj method that gets a bronse sword that weighs 10 pounds and the player's
limit is currently at 11. with your logic we would have the players 'inventory' handled
in an external method. BUT -> handle the players weight check within the player class
due to the fact that the 'state' of that player would be affected?

EG:

player.add_weight(obj.weight)

Well, in this case, I would argue that external entities have little to no business modifying the player weight. If weight is calculated based on objects, then when you add an object to the player – player.add_obj(o) or whatever – that method internally modifies the private 'weight' variable.
The idea is that clients should have to know as little as possible, and worry as little as possible, about internal details. The object in question should maintain a consistent state to the extent possible.



Of course, there are lots of details to worry about when doing things like this that cause the "clean" abstraction to start to break. Shasarak gave some examples of this like triggers firing when some action is performed. Some kind of reasonable chain of command (so to speak) needs to be set up to marshal all these events and triggers into some coherent stream.
19 Mar, 2009, JohnnyStarr wrote in the 6th comment:
Votes: 0
Thanks, i'm starting to really understand now.
So really, we want to make each class as independant as possible.
it seems that david agrees with shasarak, but david said:

Quote
EDIT: to elaborate: the point isn't necessarily to make your player object be some kind of in-code, self-contained representation of some conceptual player, with anything that player might do as a method in the code. The object is just a tool, and isn't supposed to mirror your conception of how the world works.


from the sound of what shasarak said, aren't these two view points?

i see the wisdom in using abstraction with base classes, and that the Player class shouldn't be used
as a literal 'thing', but it seems there are several lines of thought here.
19 Mar, 2009, elanthis wrote in the 7th comment:
Votes: 0
The idea of encapsulation – key to OO programming – is that your objects expose the types of mutation allowable to other objects, but everything else is done internally. David's player weight example is fairly dead on. You might have a getBaseWeight and setBaseWeight for setting the weight of the character himself (if your MUD even has any use for that kind of information), but if you're dealing only with inventory weight, you very much want nothing more than:

Player.addObject(some_object)
Player.removeObject(some_object)

If you instead expose the weight stuff as a separate set of methods then every single piece of code that adds or removes objects from the player inventory also has to remember to update the weight. At best it's just repetitive typing for no gain and at worst it's a source of very hard to track down bugs.

This stays true with a separate inventory object. In such a case, the inventory object itself is a private member of the player. Other code still uses the player add and remove methods. The addObject method might look something like:

method Player.addObject(object) {
if (this.inventory.weightSum() + object.weight >= this.weightLimit) {
return false // cannot add the object
} else {
this.inventory.add(object)
return true // we added the object
}
}


The ObjectMover stuff mentioned above is the kind of over-design that fresh-faced college grads get bullied into performing by professors who only understand CS from an academic standpoint: making that many helper classes and objects makes your design a nightmare to comprehend and a pain in the ass to maintain and extend. Real software written like that ends up being the inspiration for many Daily WTF articles.

General rule: do the absolute least amount of work and code necessary that still retains 100% correct results and is capable of doing everything you want. A corollary of that rule: make sure you know what you want your code to do before you start writing it, otherwise you end up having to rewrite it to add necessary features. A second corollary of that rule: don't start implementing design patterns and abstractions for things unless you actually know you need an abstraction or the design pattern actually simplifies the code.
19 Mar, 2009, David Haley wrote in the 8th comment:
Votes: 0
elanthis said:
General rule: do the absolute least amount of work and code necessary that still retains 100% correct results and is capable of doing everything you want. A corollary of that rule: make sure you know what you want your code to do before you start writing it, otherwise you end up having to rewrite it to add necessary features. A second corollary of that rule: don't start implementing design patterns and abstractions for things unless you actually know you need an abstraction or the design pattern actually simplifies the code.

+1 * 100

Have you read the article on how Javaland is the Kingdom o..., and verbs are just second-class citizens? Kind of slow to get to its point, but I thought it was at least amusing and at best rather insightful. It applies to all languages/patterns that enforce absolutely rigid OO patterns, not just Java.
20 Mar, 2009, shasarak wrote in the 9th comment:
Votes: 0
Quote
i see the wisdom in using abstraction with base classes, and that the Player class shouldn't be used
as a literal 'thing', but it seems there are several lines of thought here.

To put it in two other related ways:

1) Any one class should know as little as possible about how any other class works.

2) A class should have public methods which allow other objects to tell it what is going on; but the class itself should decide how it responds to that information. (Or, in the case of a returned value: a class should have public methods which allows other objects to request things, but the class itself should decide how to obtain them).

So, in the case of David's example about objects in a player's inventory and weight, other objects should be able to tell the player object that something is being added to its inventory; but it is the responsibility of the player object to decide how its internal state needs to change as a consequence of that happening. Thus, the player has a public method add_object_to_inventory(obj), which is called by another object, but the update of the total weight carried is done by the player object itself. If it doesn't work like that then the object calling add_object_to_inventory would have to be aware of the fact that players have a total_weight_carried property, which breaks encapsulation.

Having said all that, you may not want to keep a running total of the player's encumbrance anyway - you may want to calculate it by adding up the weights of everything in his inventory every time you need to know the total. If you don't do that then you get problems with an object like a water flask which is heavier when it is full than when it is empty. This means that the total weight in the player's inventory changes as a result of the player pouring out the water. If you keep a constant track of the total load, then that means that emptying the flask has to tell the player object to update its encumbrance. And, in the same way, so does any other action which affects the player's total load, which could get quite nasty after a while, especially if you have things like flasks that leak, or magical rings which get heavier if you're in a room that is closer to the place where the ring was originally forged. This means too many objects have to be aware that the player maintains a total encumbrance value, and the ring has to constantly be updated every time the player moves to a different room. It may be better encapsulation if the flask or ring simply keeps track of its own weight, and you add up the total weight of all relevant objects whenever it's needed.
20 Mar, 2009, shasarak wrote in the 10th comment:
Votes: 0
elanthis said:
General rule: do the absolute least amount of work and code necessary that still retains 100% correct results and is capable of doing everything you want.

That's obviously good advice: there is no point in adding complexity on the off-chance that it may become useful in the future; leave it out now, and add it in as and when you need it. (No object model is ever correct the first time anyway).

The reason I went for a fairly complex object model in my above example was to get the point across that the original question asked in this thread - should the code be in location A or B? - is not really valid. Almost any operation of any interest is likely to involve a number of method invocations on a number of different objects of several classes. Each separate object should handle the aspect of the operation that it makes sense for it be responsible for, and nothing else. If you start thinking in terms of all of the code for a single operation being in any one place, you'll hit trouble no matter where you decide to put it.
0.0/10