|
Prev: Name of construct
Next: Deriving - .NET example
From: H. S. Lahman on 10 Sep 2006 13:17 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. I agree with you that casting is usually not a good idea. In this case it is fairly benign because one has taken pains to ensure that it is done correctly by duplicating the relationship so that one explicitly navigates the right relationship in the code. In general casts based on static structure are less likely go awry than those that depend on dynamics for correctness because static structure tends to be more stable over time. But even here one is sacrificing some safety because some later maintainer could modify the relationships and the resulting problem would be pretty well hidden. >>>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. OK. > > 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. OK. But that is an argument for keeping the collection interface simple. If there are potentially many contexts where a Property needs to be accessed, it becomes difficult to predict exactly why it is needed. That is pretty much why peer-to-peer collaboration is mandated in OOA/D -- you want to deal with collaboration on a context-by-context basis. If the collection interface sticks to the very generic task of list management, then it remains independent of the context of access of Properties. > 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. There are a couple of philosophical issues here. So get a six-pack and get comfortable... My first point is that you would probably have that problem anyway. Given the way we are talking about accessing Properties via a PropertyType value, one way the Client is going to access a Property it shouldn't have is because of a software bug and you are going to have to deal with that anyway. The only other way to go awry is for the Client to /ask/ for the wrong property type, which is what you are trying to protect against. I address that in the third issue below. My second issue is related to how much trouble it is to deal with a refusal in an asynchronous solution. In OOA/D knowledge and behavior are treated very differently. The main way that is manifested is in the execution model for access that the developer assumes. Knowledge attributes are assumed to be accessed synchronously (i.e., the caller pauses execution until the value is provided). Behavior access, OTOH, is assumed to be asynchronous (i.e., the message sender continues to execute after the message is sent and may complete before the response even begins). There are two reasons for this. The first is that message and method are separated in OOA/D so there is potentially a delay between when a message is generated and when a response is executed. The second reason is that an asynchronous behavior model is the most general (i.e., a synchronous implementation is a special case of asynchronous where the order of messages is predefined). That is, an asynchronous model can always be implemented in a synchronous environment without change but a synchronous model usually cannot be implemented in an asynchronous environment without modification. [The OOPLs are inherently synchronous because they are 3GLs based on procedural message passing and stack-based structure. So the OOA/D is also providing some independence from the 3GL level of abstraction.] So my second point is that a proper OOA/D solution will already be asynchronous so it is guaranteed to work if there is a refusal. IOW, that comes "for free" with good OOA/D construction. All you would have to do is recognize that a refusal could happen and provide support for that in the Client. (In your case that would probably be throwing an exception since it shouldn't happen.) My third issue is the reason that one wants to prevent Clients for accessing certain properties. In reality the developer is deciding what properties clients can access and the developer can screw that up anyway by using the wrong interface, etc.. For that reason I regard language-based information hiding mechanisms as more of a message to future maintainers that says, "I had a reason for re
From: H. S. Lahman on 10 Sep 2006 14:41 Responding to Guild... > 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]----+ > > <snip> > > 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. OK, I understand the problem you want addressed. BUt first let's look at what a single factory object does when construction a Composite tree. Let's assume it is reading from something like an XML hierarchical text specification and it needs to create the individual objects and relationships. To do that it accepts an element specification from the text file and parses it. In so doing the element is identified as, say, a Rectangle. The factory creates a Rectangle object using the proper constructor, initializes it with the data values from the file, and instantiates the R1 relationship because it was smart enough to keep track of the Picture it is decomposing. To do that the factory objects needs to know two things about what it is building: the what the current object is (a Rectangle) and it must keep track of Pictures separately. That understanding of what the element in hand is irrelevant to navigating the tree during collaborations in the problem solution because the semantics is accessed polymorphically. But to /construct/ the tree the factory must understand what it is constructing (i.e., to invoke the proper constructor). I submit that writing the Composite tree back out to persistence is a symmetrical operation with the factory (deconstruction, if you will). To do that it must provide text-based identity for the elements. So the deconstructor needs to understand what the element is. I'm sure you are with me so far; I'm just making the point that deconstructing the Composite is orthogonal to the way the Composite is navigated in the problem solution. So... > > 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. Right. If you don't use Serializer composition that allows you to "walk" the structure object-by-object using polymorphic access, you need to know what you are deconstructing. Just as a factory object needs to know what the object is to invoke the correct constructor, the persistence object needs to know what the object is to invoke the right deconstruction method. Note that in this context the Composite is essentially an arbitrary stream of objects that must be processed by the persistence object. As I mentioned early, most OOPLs have no elegant mechanism for dealing with such streams. So one is at the mercy of kludges like _dynamic_cast, which can easily be abused. The key thing to remember here is that this sort of deconstruction is orthogonal to the reason you have the Composite in the first place. That is, it has nothing to do with the problem the application is solving. (More precisely, the relationship is quite indirect; there are probably persistence requirements that need to be resolved.) What you are really doing is processing a stream of arbitrary objects and you need to dispatch to the correct processing for each object. In that context it is fair to look at the type of the object, just as the factory object looks at the type of the object in its input stream to decide what constructor to invoke. So this is a context (object stream processing) where _dynamic_cast is justified -- simply because the language doesn't provide anything better. [As it happens, I would probably opt to embed the text file identity in the Composite objects when they are built and dispatch on that. There are a couple of advantages. If one is clever about identity, it will be more efficient because one can do a single table lookup for the dispatch. Much more important, though, is that it provides a cleaner mechanism for synchronization. Now the identity mapping for what to build/process is synchronized through the persistence data store.] I would also point out is there there is a dispatch to the right processing for persistence of a particular object in both Serializer and my "deconstructor" approach. In Serializer it is elegantly polymorphic because a common interface to the grunt code was provided. In fact, you can do exactly the same thing by providing the same common interface for the deconstruction methods: * parses R1 1 [ObjectA] --------------------- [Deconstructor] + saveIt() A | R2 +----------------+-----------... | | [ConcreteA] [ConcreteB] 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. So the difference comes down to how one identifies the object in hand and the indirection of the R1 polymorphic dispatch. For the reasons above I don't see the identity check as a major problem because of the orthogonal context to the problem solution. Overall I think it is a pretty small price to pay for better cohesion, separation of concerns, and isolation of persistence mechanisms. >>>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 hav
From: Brendan Guild on 10 Sep 2006 15:01 H. S. Lahman wrote in news:nMXMg.6620$wj2.5385(a)trndny06: > Responding to Guild... >>>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. > > There are a couple of philosophical issues here. So get a > six-pack and get comfortable... You have certainly given me a lot to think about! I have no doubt that I will be heavily considering your advice. > Having said all this I now have to push back and ask: Why do you > feel you need to restrict access to Properties? There are two reasons that I originally sought to restrict access to Properties. The first is that I know in advance that there will be an unmanageably large number of properties. I concluded that if I could in any manner prevent certain procedures from accessing and modifying any but a small subset of the properties, it would simplify debugging. The second reason is something which I decided to not mention because it introduces another design complexity. I would like to generate new property types during calculations that can be used on existing entities without modification. By this I mean, property types that generate properties when needed from existing properties instead of simply looking up the property in a collection. Here's a diagram: * sums [PropertyType]-----------------+ A | | | +----------+----------+ | | | 1 | [PrimitivePropertyType] [SumPropertyType]----+ It's the Composite pattern that I like so much. When someone requests a property from an entity, the collection first checks for the existence of the property, but if it does not exist, then an abstract method of PropertyType is called to compute the property as needed, given the collection object itself. For example, suppose we had three property types: 'weight', 'body mass', 'armor mass'. The value of the 'weight' property should always be the sum of the 'body mass' and the 'armor mass'. Instead of storing 'weight' and keeping it updated, or calculating it as needed, I create a new property type object called 'weight' that acts just like any other property type, except for the fact that no actual property exists in the collection. > OK, but I think the OO paradigm is pretty clear about how it > defines a property. An object property is a responsibility to > know or do something. In your case the properties all seem to be > knowledge responsibilities so far. That 'something' then > identifies the property, like 'height' for a knowledge attribute. > The value of the property distinguishes what the objects sharing > that property actually know. I apologize for any confusion. I know that a lot have terms have been taken for use in OO thinking, including 'entity' and 'property'. I have been using them as domain terms instead of as technical OO terms. > So being 4 feet tall is not a different property than being 10 > feet tall in OO terms. It is simply a different value of the same > knowledge responsibility. [snip] > What I am now wondering about is why we need A-V pairs or any > other dynamic assignment of properties at all. Why isn't 'height' > an attribute of Gnome? Then you can have 40 gnomes with the > single 'height' property that are of 40 different heights. > Instead of "assigning a property" you just need to assign a > suitable value to the single existing attribute of each Gnome. In fact, 'height' could be an attribute of every entity. That would certainly be a more conventional way of doing things. This is fundamentally the reason that I made my original post and asked my original question. The question was, is it a good idea to do all this abstractly as object relationships instead of the usual accessors and encapsulation. Of course, I realize now that I expressed myself terribly. I am curious about what you think of my primary reason for not doing it that way: I have seen projects working in this same domain that had interfaces as large as 300 members for their entity classes. To me, that seems like a very bad thing. I wonder if you feel the same way. Even more, I wonder what strategies you would use to avoid that? > The problem I originally thought you were dealing with was that > Entity could have certain properties like skills were not inherent > to Entity and could be dynamically added. This is beginning to > sound more like a basic subclassing problem where an Entity could > have subclasses for Gnome (where 'height' is relevant) or Chariot > (where 'model' is relevant). The problem is that it is impossible to arrange these entity objects under a hierarchy of classes. Each entity needs almost the full range of collaborations with every other entity. The only place where a solid distinction can be drawn between entities is that some of them are animate and some are inanimate, and that will almost certainly be represented by two subclasses of entity. Other than that, there are no clear subclass divisions in the domain. Even if I were forced to divide the entities between classes, it would cause serious problems in maintenance because I would inevitably find reasons to regret my specific division decision when I find an object that should be in several disjoint classes. I would have to use multiple inheritance, but even that would not make things easy, because I would be dealing with an explosion of perhaps hundreds of different classes. I believe that in this case, it is not clear that having a different set of particular physical attributes is a good reason for creating a new class. Just as you do not normally create a new class of car just to represent a red car, in my domain I do not want to create a new class of entity just to represent that the entity could have a color, unlike all the other entities which for some reason have no color at
From: Brendan Guild on 10 Sep 2006 21:08 H. S. Lahman wrote in news:4%YMg.5654$xC3.2846(a)trnddc06: > Responding to Guild... > > In that context it is fair to look at the type of the object, just > as the factory object looks at the type of the object in its input > stream to decide what constructor to invoke. So this is a context > (object stream processing) where _dynamic_cast is justified -- > simply because the language doesn't provide anything better. It seems counter-intuitive that using dynamic cast would be superior to the Serializer pattern, but I suppose it makes sense! > I would also point out is there there is a dispatch to the right > processing for persistence of a particular object in both > Serializer and my "deconstructor" approach. In Serializer it is > elegantly polymorphic because a common interface to the grunt code > was provided. In fact, you can do exactly the same thing by > providing the same common interface for the deconstruction > methods: > > * parses R1 1 > [ObjectA] --------------------- [Deconstructor] > + saveIt() > A > | R2 > +----------------+-----------... > | | > [ConcreteA] [ConcreteB] > > 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. This is something I had not thought of. So each persistence object contains a reference to a strategy object for encoding itself for the persistence subsystem. That very nicely does the job without any dynamic casts, though the relationship R1 is perhaps unfortunate. Similar relations would of course be present for similar tasks, such as displaying. And there will always be classes of objects beyond my control, so for them I might to do something like this: 1 R1 contains * [MyObject] --------------------- [TheirObject] | 1 | * | R2 * | 1 +---------------------------- [Deconstructor] Where objects of class MyObject is in a relationship with objects of class TheirObject, so when it comes time to encode MyObject, I need to encode all the related TheirObjects. Since I do not control TheirObject, I cannot install a strategy inside TheirObject, but I can use the R2 relationship to find all the relevant Dconstructors. > As I reasoned in the other post, I think information hiding is > overrated except when explicit requirement restrictions on access > exist and then I would already have put "hard" mechanisms in > place. IOW, I usually provide read access of all attributes as a > matter of course in defining interfaces unless there are specific > requirements to the contrary. Information hiding is also useful for proving correctness. Some objects have much more complicated internal state than the external state, and one great benefit of it is that you can be sure that the behaviour of other objects will not depend on the complicated internal state, only on the simple external state. That could save a lot of time in black-box testing, for example, and it just allows for fewer possible interactions between objects which makes thinking about the possible ways for things to go wrong easier. Naturally, you do not really give access to all attributes, only those which are conceptually part of the object. For example, if you were implementing a String object you would conceptually have a 'length' attribute and a 'charAt' index for getting the characters. You can imagine that on the inside the string might be implement using a null-terminator, so there is no actual 'length' attribute, but there would be a 'length' accessor in the interface even so. Even more strange, there could be an identifier attribute for the null- terminator so that the actual value of 'null' might vary from object to object, but that would surely not have an accessor. What the interface suggests about the implementation and the actual implementation can be quite different.
From: Dmitry A. Kazakov on 11 Sep 2006 05:34
On Sat, 09 Sep 2006 17:20:27 GMT, Brendan Guild wrote: > 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? You are mixing different issues. How many colors a car cold have? vs. How many types of colors do exist? [*] ------ * There could be colors beyond the R,G,B body. You could have alpha channel (transparency), reflection shape, change pattern, etc. >> 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. So you have to choose whether Entity is a type-specific (knows the specific property type) container or a class-wide container (knows only the class of properties). > 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. I doubt you could make "doesn't know" model very useful. If Entity is just a container of properties, then setting a property cannot influence Entity in any way, other than element change. But, normally, it should do something upon Set, or even Get. So either Property has to know Entity or reverse. The latter case is MD, the former case is mix-in, i.e. Property acts as a reference to Entity and delegates Set to Entity as a sequence of actions on Entity. >> 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. You are confusing creation of an object of the type Property with attaching it to Entity. When you call Get it returns a new object of the type Property, which is a copy of some other object, contained by Entity. If Property are big, you might wish to use a referential semantics, based on some sort of GC, etc. BUT semantically that would change nothing, because the returned Property (a handle, smart pointer to some Property_Implementation, invisible to the clients) still had value semantics. > 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. It is too loose to my taste. I see no behavior - is "1 year old" really a property of entity or just a perception of the entity by another entity? Then, what was the value of "1 year old" before one year passed? Could a client query it? This boils down to the same question "what does Entity know." ["Get should fail" is a vast theme, I leave it open for a while ] > 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. You know, MD is a fundamental thing, either you have it or not. When you do, then even if you weren't aware of that, it will proliferate your design in the form of quite ugly patterns. BTW, the check is very simple, draw a chart: Entry'Class \ Property'Class. It is a simple matrix rows are types of entities, columns are types of properties. When this matrix is not a permutation matrix (cannot be reordered to a diagonal by shuffling columns and rows), then you have it. >> 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. 2 is for free. 3 requires some sort of naming / indexing / iterating of properties. I would try to get rid of 3 or at least to split properties into immanent and transient. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de |