|
Prev: Name of construct
Next: Deriving - .NET example
From: Brendan Guild on 9 Sep 2006 13:20 Dmitry A. Kazakov wrote in news:1j5yumy5pyxg3$.k6yp5zbgutn4$.dlg(a)40tude.net: > On Sat, 09 Sep 2006 08:29:56 GMT, Brendan Guild wrote: >> First, each property type would have to be a subclass of >> Property. That will end up being a very large number of classes. >> Most properties are just slots to store numbers, such as 'height' >> and 'age', so many of those classes would be identical, just a >> number. It seems like a waste to have many identical classes just >> to represent the different property types. > > Height and Age aren't identical. Number could an implementation of > both, which does not make these types same. You can't add hours > and meters, can you? That is true, but I am still not certain that I should make these different types of properties into different classes rather than merely giving them a member which distinguishes them. If your domain was about cars, you could make red cars and black cars into to different classes, but wouldn't it be better to just have a color attribute? I plan to have a lot of different 'colors' of properties, far more than I ever expected to have classes. > Then Properties of Entity are not the interface properties. For > whatever reason you decided to separate them. Maybe because you > needed a common interface for all Properties? Then why do you > complain that Properties would be in the same class hierarchy? > These are equivalent things. I want a common interface between all Entities. I am not sure if I need it, that is how I have chosen to deal with having a domain full of a large number of entities all with a large number of wildly different properties and all interacting with each other. I'm not complaining that the properties would be in the same class hierarchy. In fact, I think they should all be in the same class, or perhaps a handful of classes. > Aha, that's a fundamental question! Does Entity "know" its > Properties? If it does then you have that sort of combinatorial > stuff. It is does not, then you can attach arbitrary properties to > arbitrary entities, that would in effect kill MD stuff and cut two > types hierarchies apart. That would be very desirable, of course, > if were possible. Unfortunately, it is usually not. Entity acts like a collection class for Properties, in the single dispatch design. However, whether it knows about the properties is a difficult question. Each property has its own semantics and it must be created and used deliberately by someone, depending on what is needed in each part of the game. Entities might send Properties on themselves and on each other, so those properties would certainly be known, but others might not be. > This or that way, but you have to determine the expected type. If > you do it using Type_ID, Type_Name etc, that inevitably would > resolve in some sort of factory call. The only difference is where > you would place the factory. So what are the use cases? Is the > factory in Entity or in the caller's context? The properties cannot be created in response to a Get call. The purpose of Get is to query the entity for the specific property that it has or does not have of a given property type. If the entity does not already contain the property object then Get should fail. The properties are created in a factory when the Entity is created, and also whenever the Entity changes during the events of the game. For example, an entity might start out with the property '6 feet tall', so that property would be created by the factory. Just for example, a year might pass and then a new property might be created by whatever is looking after time: 1 year old. Then the '1 year old' property would be given to the entity to mark its birthday. >> The problem is that it does not really know its type, it only >> behaves polymorphically as if it knows it. I want to be able to >> query the property for its type so that I can write just one Set >> procedure that works on all property types and sorts them >> according to type. The double dispatch way would have me writing >> a Set procedure for every property type, and it would completely >> prevent me from creating new property types during execution. > > The above is difficult to me to sort out. > > Firstly, how can you do Set without implementing it? If you mean > that Set should be automatically constructed from predefined type > assignment, then there is no problem to have it. Make Set defined > on the class, and MD will automatically sink down to the > assignment. That is good to know. Perhaps it is practical to use MD after all. However, it still seems as though it is being recommended only because it is the right thing to do and not for any expected practical benefits. > Secondly, by creating new properties, do you mean: > > 1. new types of properties > 2. new instances of properties > 3. attaching new properties to existing entities > > Only 1 requires a fully dynamic type system. I cannot see where I mentioned creating new properties so I must answer that question out of context. First, if I had meant creating new types of properties, I would almost certainly have said 'creating new property types'. I think I could have meant number 3, but I most likely meant number 2.
From: H. S. Lahman on 9 Sep 2006 15:11 Responding to Guild... >>Responding to Guild... >>You might have use of a propertyType attribute of [Property] here >>to distinguish, say, A-V properties from multi-valued properties. >>Ordinarily that is a no-no because the generalization relationship >>is not navigable (there is only one object in hand). So a Client >>with a Property in hand should only be able to access the common >>properties defined for the superclass, not specialized subclass >>properties. IOW, a propertyType attribute opens a back door to >>abuse that allows the client to "peek" at the specialization. > > > Alternatively, I could hardwire the various classes of properties > into the interface of the collection class. I know that the > properties of any particular object will very frequently in > maintenance and perhaps even at runtime, but adding new classes of > properties could be much more rare. > > For example: > class PropertySet { > AVProperty get(AVPropertyType); > void set(AVPropertyType, AVProperty); > MultipleValueProperty get(MultipleValuePropertyType); > void set(MultipleValuePropertyType, MultipleValueProperty); > // Similar methods for adding properties... > } > > This allows various classes of properties without dynamic casting. > Clearly the cost is serious, but unlike the rather dynamic nature of > each individual property type, I feel confident that not too many > unexpected classes of property types would appear during maintenance. The problem here is that, as you note, you are "hard-wiring" the implementation in the interface. However unlikely it might seem, if a new class of properties is needed you need to muck with the collection interface. [After a lot of years in this business I could give you some marvelous horror stories about things that would never, ever change and did. B-)] I believe you would be better off dealing with the additions using subclassing of [Property] and relationships to the subclasses to ensure the Property was interpreted correctly. That allows the collection object to deal only with [Property] objects, so it doesn't need to change when new classes are added. Thus, [Property] A | R1 +-------------------+--------------------------+ | | [AVProperty] [MultiValuedProperty] | 1 | 1 | accesses | accesses | | | R2 | R3 | | | 1 | 1 [ClientWhoNeedsAV] [ClientWhoNeedsMultiple] The R2 and R3 relationships "hard-wire" the same rule about participation. In addition, [PropertySet] has actually instantiated the relationship by returning the right flavor of [Property]. However, there is a subtle difference. The specific Client is actually making the choice by knowing what the PropertyType value implies about how the property should be interpreted. The collection class is just doing what it is told and the participation rule is implicit in defining what flavor of [Property] the PropertyType value maps to. So, in effect, one has reduced the rule enforcement to correctly mapping PropertyType. Now consider the situation where the same Client may need to access both AV and MultiValue Properties. How is the participation rule enforced? The Client must navigate the correct relationship, R2 vs. R3. In fact, both relationships will physically address the same collection object. But semantically they are different. That allows one to provide a mapping to the [Property] flavor based on the relationship rather than the subclass of [Property]. So one could have code in the Client like: PropertySet* myR2Set, myR3Set; enum PropertyType propertyType; AVProperty* myAVProperty; MVProperty* myMVProperty; .... // need AV property propertyType = HEIGHT; myAVProperty = (AVProperty*) myR2Set->getProperty(propertyType); .... // need Multi property proertyType = COMBAT_VALUES myMVProperty = (MVProperty*) myR3Set->getProperty(propertyType); So how are these casts different than using _dynamic_cast? The difference lies in the context. Here the cast is tied to the implementation of /static/ structure that cannot change during the execution of the application. Therefore the cast is quite safe (provided the developer has properly mapped COMBAT_VALUES, which is also part of the static structure). IOW, we have captured the participation rule in the static structure. That is clearly safer than _dynamic_cast. But how does it improve robustness in the face of volatile requirements? The answer lies mainly in separation of concerns because we have left [PropertySet] out of the decision process so it never needs to change no matter what new [Property] flavors one adds. All the decisions are in [Client]. So the only one who needs to change is a Client that needs to collaborate with a /new/ property flavor. But that Client will always have to change to use to the new flavor anyway. >>The collection object that manages the R1 collection needs to >>respond to requests for a particular property. To do that it >>needs some sort of identity for the desired property to >>distinguish it from other properties in its list. You can do that >>two ways: >> >>(1) an embedded identity attribute in [Property] that the search >>algorithm compares to. >> >>(2) an internal ordered list that is effectively a 2D array of the >>property type code provided when the property was added plus the >>actual property value. Now a [Property] class representing an A-V >>pair is unnecessary in your scalar property situation. >> >>I submit that the first is much more robust. The problem is that >>the second "hard-wires" a particular implementation of properties >>into the collection class. For example, suppose later on you >>decide that you do need multi-valued properties. Now (2) is a big >>problem because you need more and variable dimensions on your >>array. But (1) is completely unaffected because all the >>collection cares
From: Brendan Guild on 9 Sep 2006 17:02 H. S. Lahman wrote in news:AlEMg.58$yc4.28(a)trndny01: > Responding to Guild... > > I believe you would be better off dealing with the additions using > subclassing of [Property] and relationships to the subclasses to > ensure the Property was interpreted correctly. That allows the > collection object to deal only with [Property] objects, so it > doesn't need to change when new classes are added. I have been experimenting with these ideas as we speak. I have tried to avoid doing it the way you suggest because I felt that casting was always a bad idea, but avoiding it in this case was causing me problems. So, I am quite happy to conclude that you have the best idea here; casting is appropriate in this case. Only the client truly knows what each property represents, so it is surely best to leave the responsibility for keeping track of the class of the property entirely in the client. >> In the big picture completely replacing the details of how the >> collection class works internally would be an extremely small job >> compared to even the slightest modification to the client. The >> client is huge and the collection class is drop in the ocean, so >> I am not concerned about that sort of detail. > > My first pushback is: why is the client "huge"? That usually > indicates a god or controller object, which is a no-no in a > well-formed OO application. Perhaps it is a my misinterpretation of your terminology, but when I used the word 'client' I did not strictly mean some object that uses the entity. To me, a client can just as easily be a system of objects as a single object. When I said that the client was huge, I meant only that the collection class represents a very small amount of my effort relative to all the places in which it will be used. Even though the collection class will have instances throughout the design, the collection class alone does not even begin to solve the domain problem; it is merely a vital tool. So the client is very large because the client represents the entire solution while the collection is one vital and frequent but small piece. > My second pushback is that the code in the client to access a > [Property] is not significant. Even the code for multiple > relationships in my example above is trivial. (The Client still > needs to know what to do with the Property once it is accessed in > any case.) That's very true. I hesitate when I think about in how many properties I might have and in how many places they will be accessed, likely to be a number proportional to the total size of the design, but I recognize that a few keystrokes are nothing compared to a robust design in the end. >> It seems you have a different sort of limitation in mind that is >> not destroyed by making property types global, but I have yet to >> derive it from your words. > > You are correct that I misunderstood the problem (though with > 20/20 hindsight you stated it clearly enough). But I still think > there are other ways to approach it. You have given me new and very interesting ideas about how to restrict access to inappropriate properties. I will need to carefully study my domain to determine the exact technique that I should use now that I see that there are many options. I am concerned by the techniques that you have offered that use a runtime security check: > The downside, of course, is that the client needs to be able to > gracefully accept refusal of its request if it asks for a property > that it should not have access to. That may get a bit tricky in > an asynchronous environment where there is no immediate error > return. This seems like an awfully serious downside, especially when we have techniques available that make it impossible for the client to ask for such a property. >> Changing the state of a property with 'set' or changing the >> relationships between an entity and its properties with 'add' >> have very little difference. I have no intention of making the >> identity of properties important, so only their content matters. >> If I change the property of a gnome's height from 4 feet tall to >> 10 feet tall by altering the number contained in the property or >> by replacing the '4 feet tall' property by a '10 feet tall' >> property, the change will have identical semantics. > > It appears that we have a major disconnect here. Previously when > you talked about this I didn't take this view of 'property' > seriously because I saw '4 feet tall' as just a text string value > to substitute for a numeric value of 4. (In OOA/D where I hang > out this is not an issue because one always uses ADTs that > abstract away such OOP implementation issues.) However, I have to > conclude that you really do see '4 feet tall' as a different > property than '10 feet tall'. Surely that is not very surprising. Setting aside whatever I might want the class Property to represent in my design, just using the natural meaning of the word 'property', the property of being 4 feet tall is very different from the property of being 10 feet tall (6 feet different, in fact!) The connection between them is not that they are the same property, but that they are of the same property type which I would call 'height'. I know that the word 'property' is rather vague, but I had thought we were using it in the same way. > If so, I'm afraid I can't agree. If that were true and you had 40 > Gnome instances, each of different height you would need 40 > different properties. Surely this cannot be avoided. How else would you put the varying information of their heights into the corresponding collection objects? 40 different heights must be represented by 40 different objects, each containing a different number to indicate the actual dimensions of the gnome. Since those objects are called 'properties' and they are of the Property class, that makes 40 different properties. However, the properties have more in common than they have distinct. All 40 properties would be objects of the same concrete class and they would all have the same property type. The only things they would not share would be the actual number and their identity. > So now I am concerned whether there is any dynamic assignment of > Properties to Entities at all. Do you have any properties like > "height" or "weight" that some Entities
From: H. S. Lahman on 9 Sep 2006 21:49 Responding to Guild... >>You don't have to use a different subsystem (as I suggested later >>in the message, though, that is usually a very good idea). All >>you have to do is avoid composition where one puts all the >>responsibilities in a single object. You can easily provide a >>object that is a surrogate object for the Xxxx-able service and >>let it extract the attribute values it needs. >> >>Note that such an object is essentially a sort of factory object >>in reverse. If you employ factory objects to instantiate the >>object and initialize the attribute values (which is a real good >>OO practice), surely you can use another object to extract the >>attribute value for persistence, display, etc. It just doesn't >>look as elegant. > > > I am not sure of that at all. I like to make heavy use of composition > and polymorphism in the factory for my objects. I feel that gives me > great flexibility and simplicity in providing a solution for the > problem in the domain. For example, the Decorator pattern and the > Composite pattern. > > I cannot see a way to do a reverse factory for such objects, short of > many many dynamic casts, but I have no doubt that is not what you > were thinking of. What does a factory object do? It creates the object and then writes values to initialize the attributes, which includes addresses for referential attributes to instantiate <unconditional> relationships. The OOPL makes that convenient by combining object creation with initialization in a single constructor syntax element. One might model that in OOA/D via: * invokes 1 1 creates * [Client] -------------- [ServiceFactory] ---------------- [Service] What do you need to do to persist an object? You need to read its attributes, convert referential attributes to identity, and write them to the persistence mechanism. That is pretty symmetric with a constructor except you don't delete the object when done and there is no convenient language construct like a constructor. One might model that in OOA/D via: * invokes 1 1 saves * [Client] -------------- [ServicePersistor] -------------- [Service] To obtain the values to save the [ServicePersistor] method navigates the relationship to the [Service] object and reads its attributes just like any other collaboration. Converting references to object identifiers is trickier but it is essentially the same activity you would have to do in the protocol method in [Service] if [Service] was composed from [Persistable]. Then the [ServicePersistor] method writes the values to to the stream file. IOW, [ServicePersistor] has exactly the same code in it that would have been in the composed [Service] protocol methods. All you need to do is move it to another object. The [ServicePersistor] approach has two clear advantages. One is that the persistence is completely encapsulated so that there is no chance that [Service] will be affected by any of the activities. So to change persistence mechanisms all you need to do is substitute [ServicePersistor] implementations without touching anything else. The second advantage is that if the problem solution needs multiple objects to be saved, you can provide a single [ServicePersistor] instance to do that. Then the referential attribute conversions and persistence access are only done once rather than repeated in every object to be saved. (Multiple objects still need to be read, though.) A corollary to the second advantage is that you don't have to save every object. The Serializer approach is intended for saving /all/ the objects. That's because it converts referential attributes literally. Since every object in the application will be connected by referential attributes (or collection objects containing references), you have to save every object. Otherwise when the data store is read back you would have dangling referential attributes. Superficially it might seem like in a game saving everything is exactly what you want to do when the user wants to take a sleep break. But in reality that might be quite inefficient. That's because a lot of objects are likely to already be stored on an as-needed basis for particular contexts (e.g., artifacts in a particular room). Such groups of objects need to be loaded as a group and saved as a group, but not necessarily with everything else. (And most likely they don't have to be saved at all.) So you will already have custom code for setting up relationships with your other critters when they are read in. So you don't need to save those relationships at all. Of course you can deal with that in the Serializer protocol methods by not convert converting and saving certain relationships. Then you can isolate groups of objects for saving and ignore the rest. But wouldn't it be safer and more convenient to encapsulate all those rules in a single SaveRoom object? >>You are correct that one way or another one has to implement the >>same requirements. > > > I am not correct unless one has to implement the same requirements > within the classes of the objects that have the requirements. An > object that needs to persist must implement persistence, one way or > another. A persistence subsystem will go a very long way to turning a > nightmarishly complicated job of persistence into a simple matter, > but it will not allow me to get away without implementing even the > most basic persistence within the persistent object. A persistence subsystem completely decouples the persistence mechanisms from other subsystems. What one needs in the problem solution is an object that understands the context of What and When stuff needs to be saved. That object just reads the attributes, constructs a data packet with their values, and sends a message to the persistence subsystem. That object serves exactly the same function as a factory object that reads data from an external data store and instantiates objects. When the context for saving prevails, it is invoked just like a factory object is invoked when the context for instantiation prevails. It is just going in the other direction, so let's call it an "interface object" in deference to Ivar Jacobson. [BTW, note that the persistence subsystem can manage the reference
From: Brendan Guild on 10 Sep 2006 00:32
H. S. Lahman wrote in news:laKMg.904$FS.903(a)trnddc04: > Responding to Guild... > >> I agree with all of this. I can see that our goals in good design >> are the same, but you know how to achieve them while I do not. > > I suspect you are overthinking the solution because the actual > implementation is too simple. B-) All you need is an object that > has persistence responsibilities instead of instantiation > responsibilities. Unfortunately, I suspect that you are overestimating me in some way. Most solutions are simple once you see them, but they are sometimes harder to see than you expect. I may have a solution that allows me to do persistence in the way you describe, but it is not simple and it has roots that sink into the rest of the design in ways that I have not thoroughly investigated, complicating all of the design in unforeseen ways much like garbage collection complicates software using manual memory management. I refered to it towards the bottom of the post you quoted, but I am sure the technique you intend is simpler so I will not go into it any further. > Composition effectively combines that object with the object > being persisted. I am just arguing that it is better to keep them > separated in an OO context. I appreciate that argument and I itch to put the advice it contains into practice. I had hoped that I had made my difficulty clear in my previous post because I am very eager to discover the trick that I am missing, but I can see that I was not clear. I will not make that mistake again, so I will bring in an example from GoF to help me. The Composite pattern allows factories to build complex objects out of simple components, and vast objects from merely complex ones. Drawing software might use this to create pictures out of simple geometric components something like this: * contains [Graphic]---------------------+ A | | | R1 +------+-+-------+-------+ | | | | | 1 | [Line][Rectangle][Text][Picture]----+ The Graphic class represents both primitive drawing objects and collections of drawing objects so that more and more complex objects can be constructed out of objects from only these few classes. Each class contains whatever it needs to draw the appropriate image and to be friendly to persistence, it also provides accessors to everything. The Picture objects each contain a collection of Graphics that it would not normally need to offer to the client, but for persistence it will expose R1. The Graphic class has a pure virtual method that each subclass overrides to do that drawing. So you can see that with one method call any complicated image can be drawn, and the trick is simple enough: The implementation of the 'draw' method in Picture navigates R1 to call the draw method of each graphic. It is just that easy to do the drawing, but it is not so easy to do the persistence, despite the similarities between the operations and the fact that every object is completely open with everything it contains. It is time to encode a Graphic object for the persistence layer. I have the Graphics object in the form of a reference. As I understand it, the immediate next step should be obvious and simple. Here is what I resort to for lack of a better idea: I do dynamic casts on that reference, once for each possible class that derives from Graphic. If I find a Picture object, I navigate R1 and repeat the process. If I find one of the primitives, I extract the details easily and write them. If I did not know better, I would swear that this is what you would want me to do. I am expect that what I actually should do is something so obvious that it has not been mentioned yet, but I will still comb through everything that has been said in hope that I am wrong. I have never seen a persistence technique that does not either resemble Serializer or resort to something as crude as dynamic casts. >> If I ignore persistence for a moment, there is no reason to >> provide accessors to get at the state of each object in the >> common interface. Since the state will have a different form from >> object to object, it would require many accessors and much >> thinking to see how the heterogeneous collection of states could >> be accessed homogeneously. Fortunately, I do not have to make >> that state public and that is a huge simplification on the common >> interface. > > That's fine. The requirements do not require that all attributes > be exposed externally and you can enforce that with multiple > interfaces that control access. My point in this context is that > persistence /does/ introduce requirements that all attributes be > exposed externally. > So if you want to restrict access _in the solution_ you can still > do that by providing multiple interfaces (or the C++ friend kludge) > the same way you would do it for the non-persistence case. I can see now how poorly I was expression my thoughts. I would be very happy to not restrict access at all. I would expose every last implementation detail that I could to every aspect of my design if I thought it would in some way make doing persistence easier. I did not mean to suggest that I do not want to make things public because of how it might interfere with maintenance or any of the usual reasons for not making state public. I was trying to say that the very act of making state public was a task that is beyond my abilities. For example, how would I make the end-points of a Line public in the Graphic example? Naturally, the attributes themselves have no access restrictions in the definition of the Line class, but that does not make the end-points any more accessible to someone with a reference to a Graphic object. I would need some sort of accessors in the Graphic interface and they would have to be meaningful not only for Lines, but for Text as well and any unforeseen subclass of Graphic. I hope you see the difficulty that I am facing. I await your reply on the edge of my seat, because if it contains a simple solution to this issue it will be revolutionary for me. Until now I had assumed that Serializer of something like it was the best that there was! |