From: Brendan Guild on
H. S. Lahman wrote in news:gjFNg.6716$wj2.3572(a)trndny06:

> Responding to Guild...
>
>> I has assumed that each ObjectA contained a reference to some
>> ConcreteA, because then the relationship could be created by the
>> factory and navigated by the persistence procedure.
>
> Not quite. Exactly the same protocol will be defined in
> [Deconstructor] as is provided by Serializer. The ConcreteX
> methods will implement that protocol exactly like the Serializer
> stubs filled in for each ObjectX. The problem is that the single
> ConcreteA method applies to /every/ instance of ObjectA. In
> Serializer that was taken care of by the implicit 'this' pointer.
> But when we delegate [Deconstructor] to a peer object the R1
> relationship is *:1 and we have to instantiate it to write a
> particular ObjectA out.

Could you expand upon that slightly? I am not entirely sure of the
intended role of each object in the collaborations. If I understand
correctly the Deconstructor class implements methods to give
primitive values to the persistence system. The ConcreteX subclasses
inherit those methods and then use them when the call to saveIt() is
made.

Just before saveIt() is called, a relationship is instantiated to
show saveIt() what to save. You have said that instantiating
relationships by providing an object as an argument is not good
because it couples the instantiation of the relationship to the
caller of the method. But it seems as though the caller and the
instantiator will be the same in the case either way.

However, I am thinking that it would be good idea to have multiple
ConcreteA objects, one for each ObjectA, so that the ConcreteA
object's R1 relationship can be instantiated in the factory where it
can be done without a dynamic cast. I suspect that would require a
reverse pointer from ObjectA to ConcreteA as well, just to keep track
of the ConcreteA objects.
From: H. S. Lahman on
Responding to Guild...

>>Maintenance doesn't count. We use the OO paradigm precisely
>>because we anticipate as yet undefined future change and want to
>>make it easy to accommodate it when we get there. IOW, the OO
>>paradigm takes care of that already.
>>
>>My pushback is that the domain _as you know it_ probably does
>>allow categorization.
>
>
> You are surely correct that the domain allows categorization if I
> ignore maintenance. There are simply a finite number of distinct
> entities and there is nothing preventing me form working out a way to
> categorize them into classes.

That's true of most problem spaces other than computing. For example,
every person is unique in a wide variety ways. But when we abstract
Person as an object we use abstraction to extract the invariant
commonality of individuals _with respect to the problem in hand_. In
doing so we relegate detailed differences to knowledge attribute values.

All I am suggesting here is that you can apply abstraction to the
conceptual world of the game and identify useful classifications that
will describe groups of game entities abstractly.

> But I am shocked that you would deliberately ignore known maintenance
> issues in your design and simply trust in your OO design to save you.
> I find it very hard to believe that maintenance can be safely
> ignored.

Whoa. You sneaked in "known". B-) Recall I stipulated _as you know
it_. Whatever you actually know about the problem space is fair game
for abstraction, even if it isn't specifically in some SRS. Thus
knowing that you will have to add new flavors of entities is a known
aspect of game development over the product life cycle -- especially if
you plan to support sophisticated modding where it would essentially be
an explicit requirement. So it is fair to address such issues in the
design.

But there is a big difference between knowing that you will have to add
new flavors of entities and knowing what flavors you will have to add
and how they will actually interact with other entities. In the Jaws
example at the end the R5 relationship and [Transient] subclass provide
a basic structure for using animate entities as weapons. But the
details of the used entity, which ones can be used, and the way it is
selected are all details that may change over time as actual entities
are used that way.

Consider our discussion of adding properties. You indicated a need to
add properties dynamically. I misunderstood Why you wanted to do that
but the requirement was clear. Moreover, it was essentially a more
detailed expression of the need to provide modding support (i.e., at the
property level). That is, we knew things had to be added dynamically
but we didn't know what they would be and how they would collaborate
with other entities.

We discussed two strategies: creating new subclasses and adding
properties dynamically via the peer A-V objects collected for each
entity. I dismissed the subclassing approach early on because it would
probably lead to a combinatorial generalization that would be fragile.
When I proposed the A-V objects you will recall that one of the benefits
that I cited was that you could add arbitrary properties without the
Entity knowing anything about them (unless it was also a client of some
of the properties). And my unrelenting emphasis on peer-to-peer
collaboration ensured that whatever service was provided and whatever
interface encapsulated it, that would be a private matter between the
client who needed that service and the service itself, regardless of how
many entities lay along the relationship path between client and service.

In that discussion I talked about a bunch of other OOA/D practices like
separating concerns of instantiation from those of collaboration, using
static structure to capture requirements, and using relationships to
limit access to knowledge. All that came to bear in making the A-V
approach to adding properties viable as a solution to the maintenance
issues -- all before you even mentioned maintenance. Thus as soon as a
broad requirement for dynamic property addition existed, the application
of basic OOA/D practices led to a solution that also solved the
maintenance problem. That's what I mean by getting maintenance "for
free" in the OOA/D.

[Caveat. That is not true in OOP. Because of the compromises the OOPLs
make with hardware computational models one has to go to extra work to
make applications maintainable. That work is represented by dependency
management refactoring _after the application is validated as correct_.
It is by no means trivial so entire books have been written about how
to do it.]

>>The issue is not the values of the attributes (though differences
>>in data domains may matter). It is whether all entities have
>>exactly the same attributes. For example, does a Wizard just cast
>>spells or does it also engage in offensive physical combat? Can
>>all NPCs engage in combat or do some always flee? Do some NPCs
>>always engage in combat but never communicate for trading, etc.?
>>Do some NPCs have unique physical characteristics that are
>>important to the game (e.g., predators with a keen sense of
>>smell)?
>
>
> Even if I can make claims using words like 'always' and 'never', I
> seriously doubt that I should use them in my design. If I were to
> actually build into my design that something could never happen, then
> how hard will it be for the maintainer to make it happen?

What I am arguing against is the alternative that you seem to be
suggesting where all entity properties are dynamically added rather than
being intrinsic properties. In effect Entity then becomes an
abstraction that is so abstract that it only has two properties:
identity and a type. (You need the type to enforce whatever rules exist
to add properties to a specific entity.) That's fine, but it has a huge
drawback in an OO context. It does not reflect the intrinsic structure
of the problem space.

In fact the problem space _as you know it_ does have identifiable
entities with intrinsic properties. More important, those entities
interact with one another in well-defined ways. Those entities and
those interactions are the essence of what the game problem space is.
But if you make all properties dynamic, then you hide tha
From: Brendan Guild on
H. S. Lahman wrote in news:ZfZNg.6147$J_2.2166(a)trndny04:

> Responding to Guild...
>
> What I am arguing against is the alternative that you seem to be
> suggesting where all entity properties are dynamically added
> rather than being intrinsic properties. In effect Entity then
> becomes an abstraction that is so abstract that it only has two
> properties: identity and a type. (You need the type to enforce
> whatever rules exist to add properties to a specific entity.)
> That's fine, but it has a huge drawback in an OO context. It does
> not reflect the intrinsic structure of the problem space.

Entities also have position in the simulated environment, and I think
we should count the 'properties' collection as a property of the
entities.

I am seriously not certain that there is very much intrinsic
structure to this problem space. These are the things that I can
currently say with absolute certainty about the problem space:

There is an environment that is represented by one or more two-
dimensional grids of environment elements. At any moment there is a
set of entities and each entity has a unique position, either as
coordinates in the environment or within or upon another entity. It
is possible to navigate positional relationships from any entity
through any number of other entities to finally find coordinates in
the environment. The set of entities and the positions of those
entities can change from moment to moment, as can any particular
environment element, but there will always be at least one entity and
there will always be an environment element for every point on every
grid.

I would like to say more, but I have deliberately excluded saying
anything that is not fundamentally true of the problem space. For
example: What is an environment element? It's a wall, an open space,
perhaps a door, perhaps other things to be added in maintenance.
That's fine, but what is a wall? It is an environment element which
prevents entities from having the corresponding position. Except
that's not true because there will be entities that can move through
walls. There are a very large number of things which I know will be
true in almost all cases, but there will also be a very large number
of exceptions.

I am trying and I will continue to try to think of additional
statements that I know will be invariably true, but an adventure game
thrives on encountering new things and exceptions to rules.

> In fact the problem space _as you know it_ does have identifiable
> entities with intrinsic properties. More important, those
> entities interact with one another in well-defined ways. Those
> entities and those interactions are the essence of what the game
> problem space is. But if you make all properties dynamic, then you
> hide that structure behind a bunch of low level mechanisms for
> property and strategy assignment.

I feel that I should point out that while it is true that there are
identifiable entities with intrinsic properties in the problem space
as I know it, there are also entities which are not at all
identifiable. Just as you said above, in any particular SRS all the
entities would be identifiable but that is not the same thing.

I see your point, however. In any particular version there will be
quite a lot of structure and invariants and categories, all of which
could be represented statically in relationship diagrams and
interaction diagrams and other design tools. What keeps me from doing
that is my fear of what changing that static structure might cost me
in maintenance.

This fear is especially heightened because of your own advice that I
should keep my design close to the problem space, and I can see very
little structure in the problem space beyond that which I described
above. It is a fantasy adventure with monsters and magical items.
What are the rules for how magical items work? That depends entirely
on the magical item. Without knowing the magical item in advance,
there is really no structure I can put upon it.

> Imagine someone else built such an application and you came in
> cold to maintain it. How would you understand what it was doing?
> How would you understand what the real problem space structure was
> at the moment? Most important of all, how would you understand
> what basic assumptions about the problem space the original author
> had made. It might be easy to add entities and properties but
> just figuring where and how to do it safely would be a nightmare.
> A large part of maintainability is an easily understood
> representation of what is going on _right now_. Without that it
> gets very tough to figure out what to fix.

This is a very serious issue and one of great mystery for me. I have
been so concerned about preventing the maintainer from having to make
systemic changes when introducing new entities that the maintenance
issue you raise here never occurred to me.

What are the ways I can balance these two maintenance issues? I do
not want to start introducing structure upon the problem space that
does not exist there just to simplify the design, but I also do not
want my design to be so nightmarishly complicated that it cannot be
maintained. Perhaps careful documentation is the solution.

> For example, let's look at whether making all properties dynamic
> really makes adding new properties significantly easier and
> whether it is worth the cost. To achieve maintainability the
> properties will have to be first class objects in a collection so
> they are decoupled from the original Entity instance. That is a
> substantial cost in the original development for infrastructure
> (class definitions, collection class, access strategies, etc.)
> compared to simply adding a property directly to the Entity in
> hand. So what is gained in compensation?
>
> Not a lot if the only goal was adding properties during
> maintenance. The Property still has an interface and any client
> that collaborates with it must use that interface. The rules of
> collaboration in the overall problem solution are exactly the
> same. Any change in the Property interface will require surgery
> in the client using the interface. The question is: How is that
> different than if the property were an intrinsic attribute of
> Entity? Answer: it isn't any different at all; you have just
> moved the interfa
From: H. S. Lahman on
Responding to Guild...

>>>I am also ensuring that they really aren't accessing those
>>>properties. You are right that they are not accessing the
>>>properties, but it is sometimes nice to be able to make the
>>>stronger claim that they cannot access the properties. So if
>>>something goes wrong and it is connected to some group of
>>>properties, I will only ever need to check those places that can
>>>access those properties.
>>
>>But why is the stronger claim nice? It only has value if you know
>>that something rude /will/ happen if the wrong properties are
>>accessed in the current context. If that is the case, then I
>>would advocate using a "hard" mechanism that forces the maintainer
>>to work at breaking things. (Better yet, which will raise an
>>exception when the developer does try to break things to make sure
>>everyone knows.)
>
>
> I cannot predict what sort of bugs might result from incorrectly
> accessing properties, but accessing one property when another is
> intended will surely result in something bad.

I think accessing the wrong property is an entirely different issue,
defect prevention. I will buy that limited interfaces could have some
value for this. However, I think other practices like good naming
conventions, proper documentation of the accessed class, and avoiding
almost-the-same-but-not-quite properties would be much more effective in
preventing such defects by careless developers.

The downside using interfaces to prevent developer mistakes in accessing
the wrong property is that it may prevent a maintainer from accessing
the right property later. That is, the maintainer needs to change a
property and believes the method in question is the right place to do
it. But the interface says that property can't be accessed there, even
when it will not do any harm. So the maintainer looks for someplace
else to make the change where the property can be accessed. But that
may be the wrong place due to data integrity constraints that limit the
change to the original method where the maintainer wanted to do it.

IOW, one is using a tool designed to prevent a different kind of mistake
(accessing the properties that will do harm if accessed) than the one
you want to prevent (accessing the wrong property). Any area where
those goals do not overlap has the potential for a new defect. In
contrast, things like naming conventions are focused on exactly the sort
of defect (accessing the wrong attribute) that you want to prevent here.

> The mechanism that I have in mind is one that you would call soft
> because it is intended to be easy to allow access to any particular
> property, but it serves the purpose of restricting an object to
> accessing only certain properties. That restriction alone has a
> simplifying effect.

My point is still that you should not restrict what properties future
maintenance /might/ need to be accessed unless you /know/ that accessing
them will do harm. And if you know that, then a "hard" mechanism would
be more appropriate because it is more difficult to circumvent.

> The property type objects are peer objects of those objects that
> access properties and a property can only be accessed if
> relationships can be navigated to get to the required property type.
> Those relationships must be instantiated when the object is created
> and as long as those relationships are instantiated correctly, the
> correct properties will be accessed.

I assume you are talking about my suggestion for explicit relationships
to [Property] subclasses. (The one where I used the cast to the right
[Property] flavor based on relationship because access was for all
Properties through the collection class.) If so, then that's fine. I
would consider that a "hard" mechanism because one must modify the
static structure and the collection class to override it.

>>That concern aside, using the Strategy pattern seems like a good
>>way to get around the too-many-clients problem above. Assuming
>>the [Strategy] represents a generic behavior that produces
>>different results based upon attribute data values for the Entity
>>in hand, then one has only a few [Strategy] objects that act as
>>clients for accessing attributes.
>
>
> It seems that we have different ideas about the purpose of the
> Strategy pattern! The Strategy class does not represent generic
> behaviour; it represents abstract behaviour with no actual behaviour
> implemented. The subclasses of Strategy implement the concrete
> behaviours and they are not generic either: each subclass implements
> a behaviour intended for a specific set of entities.

OK, I was imprecise. What I meant was that a given concrete subclass
object implemented a generic behavior that applied to all members of the
[Context] set. So one would only need one instance of each [Strategy]
subclass. The differences in results for different [Context] objects
would reflect differences in the attribute data for the Context in hand
compared to other [Context] objects.

Substance abuse alert! Where I had a more serious problem was saying
the Strategy solved the too-many-clients problem. It doesn't because a
given Strategy structure applies only to a single class of objects,
[Context]. All Strategy allows one to do is switch generic algorithms
based on run-time context.

So if one has several different [Context] classes, one would need
several different [Strategy] trees to capture the unique behaviors that
were substitutable for each [Context] class. If those behaviors all
needed to access attributes of some other [Service] class, one would
still have as many clients for [Service] as there were [Context]
classes. More even, because of the multiple [Strategy] subclasses that
each might access [Service]. So one could actually be worse off than
before vis a vis the multiple clients problem.

>>>Each main strategy class will have very many subclasses to
>>>implement all the different behaviours and the factories will
>>>assign the appropriate behaviour to the appropriate entities.
>>>These behaviour strategy classes represent the bulk of the domain
>>>and they will each have access to exactly those properties that
>>>they need, and potentially to all the properties.
>>
>>OK, the first sentence seems like a problem. This sounds like
>>every Entity and c
From: H. S. Lahman on
Responding to Guild...

>>>I has assumed that each ObjectA contained a reference to some
>>>ConcreteA, because then the relationship could be created by the
>>>factory and navigated by the persistence procedure.
>>
>>Not quite. Exactly the same protocol will be defined in
>>[Deconstructor] as is provided by Serializer. The ConcreteX
>>methods will implement that protocol exactly like the Serializer
>>stubs filled in for each ObjectX. The problem is that the single
>>ConcreteA method applies to /every/ instance of ObjectA. In
>>Serializer that was taken care of by the implicit 'this' pointer.
>>But when we delegate [Deconstructor] to a peer object the R1
>>relationship is *:1 and we have to instantiate it to write a
>>particular ObjectA out.
>
>
> Could you expand upon that slightly? I am not entirely sure of the
> intended role of each object in the collaborations. If I understand
> correctly the Deconstructor class implements methods to give
> primitive values to the persistence system. The ConcreteX subclasses
> inherit those methods and then use them when the call to saveIt() is
> made.

Just like Serializer, the superclass just defines an abstract interface
to methods like saveIt() that each subclass must implement. In the
Serializer case those methods were in the object itself so the object's
attributes were unambiguously visible via the implied 'this' pointer
when the method puts their values in a message to the persistence domain.

In [Deconstructor] each subclass instance must implement the superclass
interface as well. The implementation of that interface in each
[ConcreteX] is exactly the same as the implementation of the Serializer
interface. It reads the attributes, formats them into a message data
packet, and then ships it off to the persistence domain.

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.

>
> Just before saveIt() is called, a relationship is instantiated to
> show saveIt() what to save. You have said that instantiating
> relationships by providing an object as an argument is not good
> because it couples the instantiation of the relationship to the
> caller of the method. But it seems as though the caller and the
> instantiator will be the same in the case either way.

No, I think I said that passing object references around in
/collaborations/ is not a good idea. Assuming one implements
relationships with pointers, then they have to be instantiated by
assigning the right address to them. One can assign unconditional
relationships via constructor arguments, but there is no choice about
passing an object reference as an argument to a setter for the
referential attribute when relationships are conditional.

This gets back to the notion of separating instantiation from
collaboration. If one uses pointers to implement relationships, then
the pointer is just a referential attribute and assigning it is simply a
matter of passing the object address in a setter (or constructor)
invocation. In that context there is not expectation that anything will
actually be done with the object yet. Moreover, the context for
instantiation is likely to be quite different than that of collaboration.

But when one passes an object reference as an argument in a
collaboration, there is every expectation that it will be accessed by
the method; otherwise why pass it? That introduces a higher degree of
coupling in the collaboration because the caller needs to know that the
receiver will also collaborate with that particular object.

In this case you are correct that the caller is likely to both
instantiate R1 and invoke saveIt() because of the nature of "walking"
the [ObjectA] set. Note that I was usually careful to use qualifiers
like "likely" or "usually" when saying the instantiation context will be
different. B-) There are times when that isn't true.

However, that is not necessarily the case, even here. For example,
suppose one decides to implement that "walk" with a static class method
in [ObjectA] that will give up the instances one at a time. Now the
relationship is instantiated by that class method by returning a new
reference each time it is invoked. If ConcreteA.SaveIt() has an
internal loop over invoking that class method, then the "somebody else"
I mentioned above becomes [ObjectA] itself. Then saveIt() is invoked by
somebody else who "walks" entire classes. B-)

>
> However, I am thinking that it would be good idea to have multiple
> ConcreteA objects, one for each ObjectA, so that the ConcreteA
> object's R1 relationship can be instantiated in the factory where it
> can be done without a dynamic cast. I suspect that would require a
> reverse pointer from ObjectA to ConcreteA as well, just to keep track
> of the ConcreteA objects.

I think that would be inefficient in both space and performance because
of the heap operations. As described, there is no reason for
[ConcreteA] to have any state data. (In fact, the GoF Strategy objects
rarely have state data.) All the state data is in the [ObjectA] objects
and saveIt() is a pure behavior that eats that data.

--

*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl(a)pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info(a)pathfindermda.com for your
First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Prev: Name of construct
Next: Deriving - .NET example