Re: Abstract public member variales?



Responding to Guild...

How refreshing to have an actual problem description on this forum rather that some fragments of solution code...

I have a rather awkward problem domain and I have found a potential solution, but it is rather tentative and unsettling. The problem with it is that it exposes member variables almost directly to the user, almost as if they were public.

The domain I am dealing with is an adventure game that involves many various items and creatures. Each item or creature will have many properties that make it distinct from the others, but the nature of these properties is not all known in advance.

The entities in the game will move around and have interactions which will be affected by the properties of each entity in some manner and each interaction might make use of any fixed set of properties, up to all the properties.

Enough of these properties and interactions are known to make a working implementation, but it is guaranteed that new properties and interactions will be created in maintenance, enough so that I cannot depend on any fixed set of properties.

The natural solution is to represent each of these amorphous entities as an object and the properties of each object are dictated by the class of the object. Using many classes all the possible combinations of properties needed for the game can be created, and of course many more classes would be added in maintenance.

Right. Trying to organize Entity into a generalization relationship would probably lead to a combinatorial explosion of subclasses. If you delegate properties

1 R1 possesses *
[Entity] ---------------------- [Property]
A
|
<subclasses>

your generalization tree is limited to flavors of [Property] and the R1 relationship captures the notion of a collection of a specific set of Properties for a given Entity.

[Caveat. There is potentially a problem with the '1' multiplicity for R1 because the same properties might be relevant for different entities. However, I am guessing that Property instances are likely to have attribute values that are unique to the Entity they are associated with (e.g., a Skill system for the Entity can modify the Property values).]


Unfortunately, I don't know which entities will interact with which and I have no means of creating a reliable class hierarchy to organize them. In each interaction, each entity would need to be dynamically analyzed to determine which class it belongs to so that its properties could be accessed. I might do that with the visitor pattern or a switch statement, but I found another way.

Good. B-)


Since I cannot make any reliable relationship diagrams between the entities, I represent them all as objects of a single class, like this pseudocode:

class Entity {
Object get(Property);
void set(Property, Object);
}

In Entity I replace a potentially very large number of getters and setters by a single parameterized pair. Entity has no encapsulation and no real methods, but the great thing about it is that its public interface will never have to change during maintenance.

I assume that "Property" in the pseudo code is actually a property type code while "Object" is a reference to a property instance.

Basically you are implementing a variation of the R1 relationship above. The get/set operations would actually be methods of the R1 collection class, which would be a member of [Entity]. Assuming there is at most only one Property instance for each property type, then the R1 collection class would own the smarts for finding the right one. That provides separation of concerns and encapsulation.

Without further information I would be inclined to simplify the setter by making a [Property] have a type attribute and specializing the R1 collection class to be able to access that attribute to do its search (or provide an ordered list). Then the setter only needs the Property reference. That would allow the actual client to add properties to an entity without knowing what they were. I don't know whether that would be attractive in you particular game.


The Property class is completely featureless, with no members of any sort, so creating new properties is trivial.

I am missing something here. How can a Property be featureless if there seem to be different flavors (i.e., there is a "new" property)? There has to be something to distinguish, say, a Spell property from a Sword property.

[Note that this sort of flavorizing of properties is pretty common in games. One bundles a suite of attributes for things like Combat and Movement properties. That sort of structure is clearly reflected in the modding configuration files for games like CIV.]


At first I thought that this was a horrible design because it is making everything public, but then I realized that the member variables are not truly public because they can only be accessed when the correct Property object is available. Since I will not be making the Property objects globally available, what can access what will be very strictly controlled.

Right. R1 is just a relationship that restricts what properties a particular entity has. Whoever uses the getter necessarily has a context where collaborating with the property is important. The R1 collection class now owns the responsibilities for ensuring correct access (responding NO_SUCH_PROPERTY) and locating particular properties.

However, I would point out that the client of the getter is someone who needs to collaborate with a specific Property, not the Entity itself. So what one really has is:

[Client]
| 1
|
| R2
|
| accesses properties of
| *
[Entity]
| 1
|
| R1
|
| possesses
| *
[Property]

The collaboration path is really R2 -> R1 where [Entity] is basically just a placeholder node on the path. Thus the semantics of [Entity] is not relevant to whatever the Client wants to do with the Property.

Your application will generally be more robust if you make relationship implementation and navigation orthogonal to the actual collaborations needed to solve the problem in hand. To see this consider what abstract action language (AAL) code in a UML OOA model would look like for the [Client] method:

ref = this -> R2 -> R1 WHERE <provide identifiers for specific objects>
GENERATE M1 ref <data>

Here 'M1' is a message identifier. The first line accesses the relevant Property. The second line generates a message to the Property for the collaboration. There are a number of interesting things here. Note that there is no mention of the [Entity] and [Property] classes. The Client implementation does not need to know anything about the semantics of those classes. All it needs to know is that whoever is on the end of the R2 -> R1 path will accept an M1 message. (In a well-formed OO application all messages are announcements of something the sender did so they don't have any expectation of what will happen as a result.)

My point here is that navigating the relationship path is quite orthogonal to class semantics. Therefore one should implement relationships independently from the class semantics. That means that one should not expose the collection class interface in the [Entity] interface. That is, one doesn't need the getter/setter you provided for [Entity].

To see this, let's look at the C++ code an automatic code generator might provide for the first line in the AAL (ignoring type issues for searching sets by identity):

class Client
{
public:
OrderedSet* myR2Set;
OrderedSet* getR2Set() {return myR2set;};
void doIt();
...
}

class Entity
{
public:
OrderedSet* myR1Set;
OrderedSet* getR1Set() {return myR1Set;};
...
}

class OrderedSet
{
public:
void add(Object*, ID);
Object* get(ID);
}

Client::doIt()
{
Entity* myEntity;
Property* myProperty;
OrderedSet* myR1Set;
...
myEntity = (Entity*) myR2Set->get(<ID of desired Entity>);
myR1Set = myEntity->getR1Set();
myProperty = (Property*) myR1Set->get(<ID of desired Property>);
...
}

Now using a bunch of Object* constructs and casts is something only a code generator would love, so in manually maintained code one would have to sacrifice some of the decoupling by using specific types. [This is a common problem at the OOP level because the OOPLs are still 3GLs and their type systems make substantial compromises with the hardware computational models. So one simply can't achieve the same decoupling in an OOPL that one can achieve in an OOA model if one wants the OOPL code to be readable.] This code is also slightly wordier than the code would be if the collection set interface was directly exposed directly in the [Entity] interface.

However, there is a substantial maintainability bonus here. The interface to the collection class may very well change as more requirements are met. For example, you may have to select properties based upon other attribute values than type. When that happens you would have to change the [Entity] interface as well if the collection class' interface is exposed there. But with the above code only the Client would change since Entity.getR1Set is unaffected.

It is not a major thing, but if one is using OO in the first place, one wants maintainability. So unless there is some demonstrable problem (e.g., a verifiable performance hit), one should sacrifice keystrokes to provide as much decoupling as possible for the relationship navigation.


Then I realized that I could create more complicated properties that behave just like ordinary properties in that they get and set values, but they are actually composed of other properties and use them to compute a derived property. That realization suddenly makes this idea all the more interesting!

Exactly. You have decoupled the semantics of [Entity] from the collaboration between some Client and a specific Property. Thus the nature of any new property becomes a personal matter between it and whoever now collaborates with it and [Entity] is not involved. You do that by providing peer-to-peer collaboration between the Client and Property using generic relationship path navigation while relying the relationship instantiation to ensure one has the correct participants in the collaboration.


Does this match some design pattern that I should know about? Is it just a horrible idea?

Not so much a design pattern as simply good design. You have effectively used delegation to simplify a combinatorial [Entity] generalization. You then employ generic, peer-to-peer relationship navigation to decouple the collaboration semantics from the participation semantics. So my only quibble here is with exposing the collection class' interface in the [Entity] interface.


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

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.



Relevant Pages

  • Re: Exposing contained types.. Especially LISTS
    ... hide" B and provide an appropriate interface (in which it might ... The reason is that OO communications are supposed to be peer-to-peer; the client talks directly to the service. ... This also allows us to encapsulate things like special searches and whatnot in the collection class rather than encumbering A with them. ... For example, if Client might want a blue PricedFeatureGroup, that selection is a private matter for the R2 collection class and should have no knowledge of what flavor of PricedFeatureGroup is needed by Client in its context. ...
    (comp.object)
  • Re: Abstract public member variales?
    ... abuse that allows the client to "peek" at the specialization. ... into the interface of the collection class. ... each individual property type, I feel confident that not too many ... and policies of solution collaboration. ...
    (comp.object)
  • Re: What doesnt lend itself to OO?
    ... >>server is a pure data transfer interface. ... essentially exposing the client or service implementation. ... >>paradigms can be abstracted just like any other problem space in an OO ...
    (comp.object)
  • Re: Text terminal rendering design
    ... free to give it any object that satisfies that interface. ... giving it a real facade object if I choose. ... Facade to avoid touching the client. ... completely incompatible with this subsystem interface. ...
    (comp.object)
  • Re: Text terminal rendering design
    ... Which is exactly what would happen in your case when the interface of the implementation object changes. ... But then you need a new object to own the actual responsibility within the subsystem implementation. ... I can pop in a facade in a completely painless manner without being forced to rename or kludge anything. ... Facade to avoid touching the client. ...
    (comp.object)