|
Prev: Name of construct
Next: Deriving - .NET example
From: Brendan Guild on 13 Sep 2006 13:22 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 13 Sep 2006 15:48 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 14 Sep 2006 01:07 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 14 Sep 2006 16:27 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 14 Sep 2006 17:11
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 |