views:

383

answers:

4

In a project that I've been involved with for many years, I've gradually evolved a design pattern that's proven to be extremely useful for me. I sometimes feel I should get a bit evangelical with it, but I'd be a bit embarrassed if I tried and found out that it was just my version of somebody's old hat. I've dug through Design Patterns looking for it in vain, and I haven't run across anyone else talking about it, but my search hasn't been exhaustive.

The core idea is having a broker object that manages a set of definition objects, each definition object constituting a possible value of some complex property. As an example, you might have Car, Plane, and Generator classes that all have an EngineType. Car doesn't store its own EngineType object, it stores a reference key of some kind that states the kind of Engine it has (such as an integer or string ID). When we want to look at properties or behavior of an EngineType, say WankelEngine, we ask the EngineTypeBroker singleton object for WankelEngine's definition object, passing it the reference key. This object encapsulates whatever's interesting to know about EngineTypes, possibly simply being a property list but potentially having behavior loaded onto it as well.

So what it's facilitating is a kind of shared, loosely-coupled aggregation, where many Cars may have a WankelEngine but there is only one WankelEngine definition object (and the EngineTypeBroker can replace that object, leveraging the loose coupling into enhanced runtime morphism).

Some elements of this pattern as I use it (continuing to use EngineType as an example):

  1. There are always IsEngineType(x) and EngineType(x) functions, for determining whether a given value is a valid reference key for an EngineType and for retrieving the EngineType definition object corresponding to a reference key, respectively.
  2. I always allow multiple forms of reference key for a given EngineType, always at least a string name and the definition object itself, more often than not an integer ID, and sometimes object types that aggregate an EngineType. This helps with debugging, makes the code more flexible, and, in my particular situation, eases a lot of backward compatibility issues relative to older practices. (The usual way people used to do all this, in this project's context, was to define hashes for each property an EngineType might have and look up the properties by reference key.)
  3. Usually, each definition instance is a subclass of a general class for that definition type (i.e. WankelEngine inherits EngineType). Class files for definition objects are kept in a directory like /Def/EngineType (i.e. WankelEngine's class would be /Def/EngineType/WankelEngine). So related definitions are grouped together, and the class files resemble configuration files for the EngineType, but with the ability to define code (not typically found in configuration files).

Some trivially illustrative sample pseudocode:

class Car {

    attribute Name;
    attribute EngineTypeCode;

    object GetEngineTypeDef() {
        return EngineTypeBroker->EngineType(this->GetEngineTypeCode());
    }

    string GetDescription() {
        object def = this->GetEngineTypeDef();
        return "I am a car called " . this->GetName() . ", whose " .
            def->GetEngineTypeName() . " engine can run at " .
            def->GetEngineTypeMaxRPM() . " RPM!";
    }

}

So, is there a name out there for this?

A: 

Sounds like a combination of GoF Builder, Prototype, and maybe Featherweight to me.

duffymo
+1  A: 

It sounds like a variety of a Flyweight (many cars share a WankelEngine). But how does that make sense? Most cars have an engine, but how can many of them have the same instance of an Engine? They wouldn't get far that way. Or do you mean that many cars have an Engine of type WankelEngine? Spose that makes more sense. Then what's the use of the "WankelEngine definition object"? Is it a Factory that's building flavors of that object and passing them back to the requestor? If so it doesn't sound like a definition object, sounds more like a Factory that's taking in the parameters of the object to build and giving that object back.

I do see some good GoF practices in here, specifically that you are composing instead of inheriting (my Car has an Engine vs. my Car's Engine is a WankelEngine). I wish I could recall the quote exactly, but it's something like "inheritance breaks encapsulation" and "favor composition over inheritance".

I'm curious what problem is solved by this. I think you've added a good deal of complexity and I'm not seeing the need for such complexity. Maybe it's something specific to your language that I don't understand.

The GoF guys do discuss composing patterns into larger patterns, MVC in particular is an aggregate of three other patterns. Sounds like you've done something like that.

jcollum
Yeah, in a real implementation it'd be something more like Car has-a Engine of-a EngineType. The definition object tells you what there is to know about the EngineType. The broker definitely isn't a Factor, though; it doesn't make the definitions on-demand, it loads them from their classes.
chaos
As to problems solved: one of the main things it does, which has led to me using it for tons of things, is facilitate maintaining and extending the definitions at runtime, which is critical because the project in question is 23.9/7 uptime. In a more static project that wouldn't be important.
chaos
Sounds like a Factory pattern with a data-driven definition of object composition. If you have to recompile the code to get it to change what it builds when you ask for a new object you aren't really changing the solution, you're just shuffling things around.
jcollum
I meant: it sounds like what you really need is a Factory pattern with a data-driven definition of object composition.
jcollum
+1  A: 

SingletonRegistry

Believe me or not. I was thinking the very same thing this morning.

I have used this pattern before but I've never found a reference for it nor know how to name it.

I think is a kind of "Keyed" singleton, where the instances are stored somewhere and they are obtained using a key.

The last time I use it was to retrieve data from different sources.

I had about 50 database tables ( make it 10 ) And I have a front end "table" where the data was to be displayed, but the data could come from any of those sources and each one require different logic ( queries, joins, keys, etc. )

This front end was "configurable" so I couldn't know what values were to be shown and which other won't.

The solution was to take the columnName ( in the front end ) as the key, and get the correct instance to create the right query.

This was installed in a hash map at the beginning and later retrieved from a database table.

The code was like this:

class DataFetcher {
    abstract Object getData( Object id );
}

class CustomerNameDataFetcher extends DataFetcher {
    Object getData( Object customerId ) { 
        // select name from customer where id = ? 
     }
}

class CompanyAdressDataFetcher extends DataFetcher { 
     Object getData( Object customerId ) { // don't ask why.
          // select name from company , customer where customer.co = company.co and cu = ?  etc.
     }
} 

class ProductColor extends DataFetcher { 
     Object getData( Object x ) { 
     // join from customer to color, to company to season to a bunch of table where id = ? 
}

// And the list goes on.

Each subclass used different logic.

At runtime the user configured it's view, and select what he want to see.

When the user selected the columns to see, I used the column name and an Id to fetch the data.

The DataFetchers were all installed in the parent class ( I didn't want to have a separeate class for this ) in a class method.

class DataFetcher {
    abstract Object getData( Object id );

    private static final Map fetchers = new HashMap();static { 
        fetchers.put("customer.name", new CustomerNameDataFetcher() );
        fetchers.put("company.address", new CompanyAdressDataFetcher () );
        fetchers.put("product.color", new ProductColor () );
        ...
    }
    public static DataFetcher getFetcher( String id ) { 
        return fetchers.get( id );
    }      

}

At the end to fill the front end table I just call it like this:

pseudocode

 for each row in table 
      for each column in row
          column.text = DataFetcher.getFetcher( column.id ).getData( row.id )
       end
 end

Is it like this? Or do I misread your description and mine is quite different.

Finally I think this is called SingletonRegistry or something like that. I ( probably ) like you, created this out of necessity. Chances are this is a common pattern.

OscarRyz
Looking into it briefly, I do believe you're right. The broker definitely seems to be a Registry, and though my definitions aren't quite always singletons, they usually are. So, pattern-composition-wise, it looks like I'm using Flyweight-Singleton-Registry. Thanks!
chaos
heh heh what about Flyweight-Singleton-Registry-Factory-Handmade-Builder-Stuff... :)
OscarRyz
+2  A: 

I've used a pattern similar to this before, most often in games. I'd have a WeaponDefinition and WeaponInstance classes (not quite with those names). The WeaponDefinition class (and various subclasses, if I have different types of weapons, e.g. melee vs projectile) would be responsible for keeping track of the global data for that type of weapon (rate of fire, max ammo, name, etc) and has all the logic. The WeaponInstance class (and subclasses) contain the current state in the firing sequence (for use with comparing the rate of fire), the current ammo count, and a pointer (it could be some key into a manager class as in your example, but that doesn't seem to be a requirement of the pattern) to the WeaponDefinition. The WeaponInstance has a bunch of functions for firing, reloading, etc, that just call the appropriate method on the WeaponDefinition instance, passing itself as an argument. This means the WeaponDefinition stuff isn't dupicated for every tank/soldier/airplane in the game world, but they all have their own ammo counts, etc.

I have no idea what it's called, and I'm not sure it's quite the same as what you're talking about, but I think it's close. It's definitely useful.

rmeador