From: H. S. Lahman on
Responding to Guild...

>>Basically what I am hearing here is that you want to make this
>>game up as you go along developing the software. I don't think
>>that works.
>>
>>All software needs requirements and all software developers need
>>to understand the problem domain /before/ the software can be
>>built. Here the problem domain is a fantasy realm. You have to
>>have a clear idea of how that realm works (i.e., what the
>>requirements are) before you start designing software.
>
>
> Even so, the fact remains that large areas of the domain are fluid
> and uncertain. I know for a fact that no matter how carefully I plan
> the initial design I will need extensive maintenance. Surely you are
> not suggesting that it is impossible to create software under those
> conditions. Even though this is only a game, I cannot change the
> domain. So the question is, what is the best way to use OO design to
> assist me?

What I am pushing back on is what business reasons lead you to expect
that there will be a need for extensive maintenance? And if there is
such extensive maintenance, what would be the nature of it?

The main reason I am pushing back is because this doesn't seem to be the
direction the gaming industry is going. First, the industry is clearly
consolidating on game engines. Now almost all major games use one of a
dozen (at most) game engines and it wouldn't surprise me if there was
even more thinning of the herd because it is simply too expensive to
develop game engines due to the high content of advanced graphics and
physics, both of which should be reusable.

More important, new game releases seem to be in two camps. One is the
tweak camp where each major release just jazzes up the graphics and
provides a bunch of new units that work the same way as the old ones but
look different and have somewhat different effects on game play. The
CIV series is a classic example of this where even a game that looked as
different as Alien Crossfire basically worked exactly the same way as
all the other CIVs. And all MMOGs are in this camp.

The other is the rewrite camp where a completely different game is
produced with each release. An example is Starfury vs. Space Empires.
To do that one effectively redesigns and re-implements the game. This
originated with clone games where I-can-do-it-better rules. However, I
think a large consideration is that in good simulations the key is how
things play together. So doing something better usually involves fixing
a lot of little things that interact at the game design level so one
effectively has a fundamentally different design to implement when one
is done fixing a few things to make it better.

What I don't see are new releases of games where much of the game works
the same way but certain aspects of it are substantially changed. I've
only seen two examples of that, MOO III vs. II and Galactic
Civilizations II vs. I, and both were disasters. Both were largely the
same but some areas were modified substantially. The result was that to
the existing player base it looked like the same game but those players
found that the old ways of doing things didn't work anymore, which led
to a backlash from the installed base of players. [Interestingly in
both cases the game elements didn't change much but the UI paradigm to
manage game elements changed a lot. So it was not a matter of what one
needed to do to be successful, but how one needed to do it.]

>
> Though I would not have worded it this way, until now my plan has
> been to use an extremely abstract entity objects. For each one I
> would represent physical properties as a collection of 'Property'
> objects which are also extremely abstract, holding only a data value
> and a 'PropertyType' reference to provide the semantics of the value.
> The behaviour properties of the entities would be represented by a
> fixed set strategy objects which polymorphically dispatched event
> messages to low-level entity manipulation methods.
>
> I could implement my entire design in this way by creating strategies
> until all the desired entities were behaving as desired, with no
> difficulty in adding or modifying entities except for the complexity
> of working on such a low level.
>
> Through reading this thread, I have come to realize exactly what I
> need. Primarily, I need extremely high decoupling between entities.
> It is a priority above all others that I should be able to add or
> modify entities at will in any manner that satisfies the very
> primitive fundamental structure of the domain without forcing even
> the slightest modification to other entities.
>
> Towards this goal, each entity becomes like a subsystem with the
> properties acting as the data interface, though it is data by
> reference to accommodate the necessity of entities modifying each
> other's properties. Also, entities will naturally share classes and
> stateless objects, but otherwise they will be quite separate.
>
> Now the only problem is to overcome the complexity and maintenance
> nightmares of dealing with the low-level property manipulation. I
> suspect that this should be dealt with using your idea of role
> objects. The only place lacking abstraction currently is within the
> implementation of the entity behaviour, so let it be dealt with
> independently. The behaviour strategy for each entity can assign
> roles to each entity that it interacts with by wrapping the bare
> Entity objects in more meaningful Role objects, such as Weapon,
> Monster, Furniture, etc.
>
> Just as you suggested, when a Monster picks up a Jaws to attack with,
> the Jaws will be wrapped in a Weapon object because it is now
> conceptually a weapon. The Weapon object will implement some or all
> of the low-level property manipulation that goes along with using a
> weapon but in an abstract way. No other Entity object needs to know
> about the existence of the Weapon object.
>
> I can use object-oriented design within the implementation of an
> entity or even a large group of entities to greatly simplify the
> otherwise low-level implementation, but that design is to be
> encapsulated to always a subset of entities.
>
> If I find that my abstractions and generalization for my current
> entities are not allowing me to create a new entity which I nee
From: H. S. Lahman on
Responding to Guild...

>>The problem is reading the attributes because they are not in
>>[ConcreteA], they are in [ObjectA]. So the saveIt() method in
>>ConcreteA must navigate the R1 relationship to get to them rather
>>than the 'this' pointer. If R1 is implemented as a pointer, then
>>the address of that reference with be exactly the same as the
>>address provided by the 'this' pointer. The only difference is
>>that the 'this' pointer in Serializer is hidden while the R1
>>pointer in the deconstructor approach must be explicitly
>>implemented, instantiated, and navigated.
>>
>>In the Serializer case somebody had to "walk" the objects in
>>[ObjectA] set and invoke the Serializer's saveIT() (or whatever)
>>protocol method for each object. Somebody has to do the same
>>thing for the deconstructor approach. When they do so, they must
>>instantiate the R1 pointer to the current ObjectA in hand before
>>invoking saveIt() to be sure saveIt() gets the right attribute
>>values.
>
>
> If I understand this correctly, the ConcreteA class will have only
> one object and the persistence encoding algorithm walks through the
> relationships of the objects to be encoded. Each time it encounters
> an object it finds the appropriate 'saveIt' object, i.e. the lone
> instance of ConcreteA or ConcreteB or ConcreteC, depending on the
> class of the object to encode and using dynamic cast if polymorphism
> is involved.

Actually, I was sloppy in the model; the relationship should have been
1:1, not *:1. ConcreteA can save any member of the [ObjectA] class, but
it only does it one object at a time. I assumed some other object would
"walk" the collection of all [ObjectA] members, instantiate a 1:1 R1 for
each one (so ConcreteA could get at the data), and then would invoke
Deconstructor.saveIt(). The ConcreteA.saveIt() implementation would
navigate R1 and read the right attributes to save.

However, as I pointed out later in the message, the loop over [ObjectA]
objects could be in Deconstructor.saveIt(). Then saveIt() would only be
invoked once.

>
> Earlier, you said, "When you read the object type you can instantiate
> the R1 relationship so that ObjectA is processed by the ConcreteA
> deconstructor when saveIt() is invoked. Essentially what you have
> done is moved the polymorphic dispatch from the object to be saved
> into a GoF Strategy pattern."
>
> I am sure that the Deconstructor class and its subclasses are the
> strategies, even though the GoF specifically say that the Context
> object has a reference to its strategy. But I cannot see where
> polymorphic dispatch is involved here. I presumed that it had been
> sacrificed when we abandoned the Serializer pattern to avoid coupling
> with the persistence.

The polymorphic dispatch comes in when one used [Deconstructor] to save
objects from [ObjectB] and [ObjectC] classes. Instantiating R1
correctly also means that one has to have the right flavor of
[Deconstructor] subclass object in hand when one invokes
Deconstructor.saveIt().

IOW, subclassing [Deconstructor] and using it to save other classes than
[ObjectA] requires polymorphic dispatch of saveIt() going to the right
flavor of [Deconstructor]. That is essentially the same as inheriting
Serializer's saveIt() for the [ObjectA], [ObjectB], and [ObjectC]
classes. One invokes the same interface but gets different
implementations depending on which class one is saving.

>
> Assuming that you literally meant that the Decontructor was following
> the Strategy pattern and therefore each ObjectA must have a reference
> to the unique ConcreteA, then I have another interpretation of your
> words: the factory does choose which Deconstructor goes with each
> object and stores it in a reference for the life of the object, to be
> used by the persistence algorithm and eliminating the need for
> dynamic casts.

The R1 relationship is navigated only one-way. The ConcreteA.saveIt()
implementation needs to read the attributes in the right ObjectA but
ObjectA doesn't need to know that [Deconstructor] exists at all.

Corollary: when it comes time to save objects, whoever is running that
show talks to [Deconstructor], not [ObjectA]. (It does need to
instantiate R1 in [Deconstructor] so that Deconstructor.saveIt() can
find the right ObjectA.) IOW, whoever is running the show tells
[Deconstructor] to do the saving.

<aside>
Note that in the GoF patterns' example implementations the client talks
to [Context], not [Strategy], and [Context] relays the request to
[Strategy]. That is not good OOA/D practice because it violates
peer-to-peer collaboration by making [Context] an agent or middleman in
the collaboration.

Once the delegation is defined, the Strategy owns the relevant
properties (e.g., saveIt() in [Deconstructor]). So the client should
talk directly to [Deconstructor]. Typically that would be done by the
Client navigating a <generic> relationship path through [Context].

In this case, though, saving is likely to be an important part of the
problem solution that is orthogonal to other collaborations where the
Clients might need to talk to [Context]. In addition, the Client will
probably be an object that explicitly encapsulates the rules and
policies around saving objects. That all combines to make it rather
likely that the Client would have a direct logical relationship to
[Deconstructor]. Thus, instead of

[OrdinaryClient]
| 1
|
| R2
|
| related to
| *
[ObjectA]
| 1
| saves
|
| R1
|
| 1
[Deconstructor]

we might have something like:


[OrdinaryClient]
| 1
|
| R2
|
| related to
| * * manages persistence for
[ObjectA] -------------------------------+
| 1 | R3
| saves |
| |
| R1 |
| |
| 1 1 invokes 1 | 1
[Deconstructor] ------------------ [SavingClient]
R4

Here R3 captures the fact that whoever is running the show needs to know
which objects need saving. Here it would be logical for [SavingClient]
to have the loop over instances of [ObjectA]. However, one
From: Brendan Guild on
H. S. Lahman wrote in news:j6XOg.1601$832.1014(a)trnddc04:

> Responding to Guild...
>
> Actually, I was sloppy in the model; the relationship should have
> been 1:1, not *:1. ConcreteA can save any member of the [ObjectA]
> class, but it only does it one object at a time. I assumed some
> other object would "walk" the collection of all [ObjectA] members,
> instantiate a 1:1 R1 for each one (so ConcreteA could get at the
> data), and then would invoke Deconstructor.saveIt(). The
> ConcreteA.saveIt() implementation would navigate R1 and read the
> right attributes to save.

Now I realize that there is something else bothering me about that
diagram. Let me draw an expanded version just to be sure that I
understand correctly:

1 saves R1
[ObjectA] ------------------------+
|
1 saves R3 1 | 1
[ObjectB] ----------------- [Deconstructor]
1 | + saveIt()
1 saves R4 | A
[ObjectC] -------------------+ | R2
+---------------------+-----------------+
[ConcreteA] [ConcreteB] [ConcreteC]

Since the direction of these relationships is now clear, if I am
correct then it must be that every Deconstructor object has
references to ObjectA, ObjectB, and ObjectC, then the various
concrete deconstructors choose the appropriate reference to save so
that ConcreteA saves ObjectA and ConcreteB saves ObjectB, etc.

> The polymorphic dispatch comes in when one used [Deconstructor] to
> save objects from [ObjectB] and [ObjectC] classes. Instantiating
> R1 correctly also means that one has to have the right flavor of
> [Deconstructor] subclass object in hand when one invokes
> Deconstructor.saveIt().

There are three tasks involved in saving an object, call it A. The
first is obtaining the Deconstructor object. This requires knowledge
of the concrete class of A and probably a dynamic cast or something
similar to get it.

The second is instantiating the relationship between A and the
Deconstructor. If my diagram is correct above, it is a different
relationship for each concrete class that A might have, so either
this must be done at the same time the Deconstructor is being
obtained or another dynamic cast is needed.

The third is invoking saveIt() on the Deconstructor object, requiring
nothing but the deconstructor object itself. This is where there is
supposed to be polymorphism, but there cannot be real polymorphism if
the concrete class of the object is still known, so this must happen
in a different procedure from the first two tasks.

There must be some saving procedure that is independent of the
concrete classes involved and just performs general saving
activities, one of which is invoking saveIt() on deconstructor
objects that it gets from somewhere else.

It is not clear how the separation of the first two tasks from the
third task is expected to occur. Who knows the class of the
deconstructor and who does not? Who is forced to deal with dynamic
casts and who gets the benefit of polymorphism?

It seems rather strange that one would go to the trouble of
dynamically determining the class of the object to be saved and then
not simply call saveIt() immediately.

> IOW, subclassing [Deconstructor] and using it to save other
> classes than [ObjectA] requires polymorphic dispatch of saveIt()
> going to the right flavor of [Deconstructor].

Except that in the immediately previous sentence you implied that we
must ensure that we have the unique correct flavor of Deconstructor
if we are going to save the object we are trying to save. So what is
being called polymorphic dispatch can only go to one place, a place
we were forced to determine in advance.

> That is essentially
> the same as inheriting Serializer's saveIt() for the [ObjectA],
> [ObjectB], and [ObjectC] classes. One invokes the same interface
> but gets different implementations depending on which class one is
> saving.

It is nice in the Serializer pattern because we are not forced to
know which class we are saving. It seems to defeat the purpose of
polymorphism if we must to use a dynamic cast to get it.

>> In fact, I think this entire technique for persistence could be
>> simply described as the Serializer pattern, but with the
>> implementations of the 'Serializable' interface removed from the
>> actual Serializable class and into another object by using the
>> Strategy pattern.
>
> Bingo. Give that man a kewpie doll.

Actually, I was wrong! I think that saying this technique uses the
Strategy pattern is misleading. The Strategy pattern is defined to
include a reference from the Context object to the Strategy object,
but that is lacking in this case, so technically it is not the
Strategy pattern. In addition, it seems very tempting to put a
reference from ObjectA to ConcreteA and if we do not make it clear
that this is not actually the Strategy pattern, people will
innocently put that reference in thinking that they are following
this thread's advice.
From: Brendan Guild on
H. S. Lahman wrote in news:4mWOg.537$HZ5.52(a)trndny08:

> Responding to Guild...
>
> What I am pushing back on is what business reasons lead you to
> expect that there will be a need for extensive maintenance? And
> if there is such extensive maintenance, what would be the nature
> of it?

I see that you certainly know what you are talking about in game
design as well as OOA/D.

I believe this is my fundamental reason for needing to design the
game in this manner and for expecting extensive maintenance: The
entertainment value of the game will almost entirely come from the
diversity of situations, creatures, and items that the player will
encounter in an exploration and adventure setting. There will be no
action and relatively little strategy, but the player gets better at
the game by getting to know what could be called a jungle of
entities.

The jungle is what will require nearly all the maintenance and its
size is expected to increase with each version. The entities are
required to be diverse, and combined with an expanding set of
entities it is nearly impossible to implement them with a single
generic algorithm, though limited groups of entities might share one
algorithm if possible.

> I don't have any problems with this at this level of abstraction.
> One suggestion, though, would be that the GoF State pattern is
> probably more appropriate than the Strategy pattern. That's
> because I would bet that decoupling entities will result in
> complex protocols where multiple behaviors are invoked and where
> there may be a need for internal role state carried between those
> behaviors, which you mentioned above.

It is true that the strategy object will likely carry state in many
cases, but in the State pattern the state of the Context is
represented by different objects and the relationship between the
Context and the State is expected to be instantiated in different
ways as the state of the Context changes.

In the Strategy pattern, each pattern represents an algorithm for
solving a problem and the relationship changes only if the algorithm
that should be used changes.

In my case, each strategy object represents an intended way for the
entity to behave. Complex behaviour require that the object have
implicit state, but the intention does not change, so the
relationship is left intact.

On the other hand, if there is ever a need to dynamically replace one
strategy with another according to some state of the entity, there is
nothing preventing me from doing that. It seems like the choice
between State and Strategy is of very little consequence.

> Another notion you might think about is viewing the collaborations
> as complex activities that might warrant objects just to model the
> collaboration itself. I suspect you are already heading this way
> with your reference to an entity being a subsystem analogue. What
> I suspect might happen is that the delegated strategies might
> become complicated and need to be broken up themselves. Then a
> Facade pattern around those objects could provide better
> decoupling.

I agree. I will definitely need to use a Facade between entities.
Entities will need to give each other messages, such as when one
entity uses another entity to smash yet another entity; the two
passive entities would have to be informed. Also, modifying
properties should be done through the Facade so that each entity is
given a chance to react to each modification, instead of giving
direct access of the property collection to other entities.

> For <an extreme> example, a basic collaboration notion like one
> entity attacking another could play out as an entire sword fight.
> You could do that with individual interactions between the
> entities for each thrust and parry or you could encapsulate the
> fight itself with an infrastructure for AI strategy to sequence
> moves, graphics control, and combat rules. The former would be a
> necessity if the player controls the avatar's moves in the game
> design. The latter, though, allows very generic interactions
> between entities but requires a whole lot of infrastructure that
> shouldn't be in a single god object. The benefit lies in the fact
> that one can substitute various types of combat venues trivially
> at the entity level by simply switching Facades.

This seems very interesting! If you would like to expand upon it in
more detail, I would be very pleased to discuss it.
From: H. S. Lahman on
Responding to Guild...

>>What I am pushing back on is what business reasons lead you to
>>expect that there will be a need for extensive maintenance? And
>>if there is such extensive maintenance, what would be the nature
>>of it?
>
>
> I see that you certainly know what you are talking about in game
> design as well as OOA/D.

Actually not. I used to do commercial simulations but not games. So my
perspective is purely that of a player.

>
> I believe this is my fundamental reason for needing to design the
> game in this manner and for expecting extensive maintenance: The
> entertainment value of the game will almost entirely come from the
> diversity of situations, creatures, and items that the player will
> encounter in an exploration and adventure setting. There will be no
> action and relatively little strategy, but the player gets better at
> the game by getting to know what could be called a jungle of
> entities.

I can understand little strategy; the adventure games tend to be short
of that. B-) But surely there must be some action. The player has to
interact with the virtual world somehow or else it is just a fantasy
travelogue. You mentioned Trolls clubbing things and using Jaws as a
weapon. Are those all NPC activities that the player simply observes?

What sort of things does the player get to know? How does knowing those
things make the player better? I am pushing back on this because...

>
> The jungle is what will require nearly all the maintenance and its
> size is expected to increase with each version. The entities are
> required to be diverse, and combined with an expanding set of
> entities it is nearly impossible to implement them with a single
> generic algorithm, though limited groups of entities might share one
> algorithm if possible.

.... I am not convinced of that. For one thing a lot of games,
especially shooters, depend strongly on graphics to provide variety.
The monsters all work pretty much the same way but they have almost
infinite variety in how they look. Thus some monster may have its outr?
mouth in its abdomen, but it still uses it to eat and bite.

It seems to me that the key is for the player to /perceive/ the entities
as diverse. Graphics are one way to do that. A CIV-like scheme with
fixed properties and a wide variety of values is another. Yet another
is what I think of as the Chinese Menu system of selection (one dish
from Column A, one from Column B, etc.).

For example, it is common in defect tracking systems to classify defects
with multiple different, fairly orthogonal characteristics (e.g.,
severity, location, and nature) that play together. When classifying
the defect one selects one option from each characteristic. Each
characteristic has a few choices. So if each characteristic only has 5
choices, one still have 125 unique choices for describing a particular
defect.

Thus for a combat system one might have one characteristic to describe
attack mode (hurling objects, clubbing, biting, rendering limb from
limb, etc.), another to describe primary agility advantage (quickness,
speed, coordination), and yet another to describe primary physical
advantage (strength, size, clock-stopping ugliness, whatever). And
that's just for a combat system. Do that for other contexts, expand on
it with a primary/secondary/tertiary system to allow multiple
characteristics, and employ some fancy graphics and it seems to me you
could provide a suite of entities that were perceived to be of almost
infinite variety using a fixed set of characteristics.

Now for that to work the characteristics need to play together. So in
this example you would need a combat system that would factor them all
into the combat. So it is a nontrivial game design problem to identify
that fixed set of characteristics AND a combat system that works well.
(Clearly I am winging it in the example and the characteristics would
need a lot more thought.) But once you do that, I think that will be
largely transparent to the player.

Thus I can look at the CIV series and the configuration files and say,
"Aha! I know how they are doing that!". That's because I have 20/20
hindsight because I spent a decade doing simulations so I have a pretty
good feeling for the techniques used. But I doubt that the average
player is going to look at the variety of units, buildings, etc. and
recognize that there is actually a very simple underlying set of
characteristics providing that variety.

>>I don't have any problems with this at this level of abstraction.
>>One suggestion, though, would be that the GoF State pattern is
>>probably more appropriate than the Strategy pattern. That's
>>because I would bet that decoupling entities will result in
>>complex protocols where multiple behaviors are invoked and where
>>there may be a need for internal role state carried between those
>>behaviors, which you mentioned above.
>
>
> It is true that the strategy object will likely carry state in many
> cases, but in the State pattern the state of the Context is
> represented by different objects and the relationship between the
> Context and the State is expected to be instantiated in different
> ways as the state of the Context changes.

Yes, that is exactly what happens in role migration. Having a single
[Context] object switch roles based on context at run time is what the
GoF envisioned. But that is essentially the same as having different
objects of the [Context] set each assigned a different role once at run
time. The "context" is then just which Context object one has in hand.

> In the Strategy pattern, each pattern represents an algorithm for
> solving a problem and the relationship changes only if the algorithm
> that should be used changes.

You could also use Strategy to have different objects in the [Context]
set always use the same algorithm that was different than the one other
objects in the set used. Essentially Strategy and State are twins; the
difference lies in the number of behaviors involved and whether internal
state is required.

[FWIW, I think State was misnamed; Role would have been much better.
State suggests using it constructing state machines and it really isn't
the best way to do that. Though there are code generation tools on the
market that do exactl
First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Prev: Name of construct
Next: Deriving - .NET example