Saturday, October 30, 2010

A manager for heterogeneous data (a refactoring puzzle)

Currently I'm in the process creating a software subsystem, which is responsible for collecting data from multiple places, and then notifying some listener-type entities when that data has been received.

It looks something like this.

Pusher classes => __raw data__ => DataManager => __processed data__ => listener classes

So far so good. The left half of this graph seems to be easy to structure, as the DataManager simply provides  a few public functions to push different kinds of data, and those are the calls made from the pusher classes. Even though those functions have different signatures for different types of data, there's only one place of access, namely the manager itself.

The right side of the graph is a bit more problematic. That's because every listener listens to different types of data. Thus the function signatures of the listeners have to reflect the signatures of the corresponding DataManager functions, creating a dependency, and a maintenance nightmare.

So my code currently looks like this.

ZDataManager
{
    //heterogeneous listener registration / unregistration
    void RegisterFruitListener(ZFruitListener*);
    void RegisterVegetableListener(ZVegetableListener*);
    void RegisterBerryListener(ZBerryListener*);
    //...plus corresponding unregistration functions
    
    //heterogeneous receiver functions for receiving different types of data
    void ReceiveNewFruit(ETexture, EColor, ETaste);
    void ReceiveNewVegetable(string sVeggieName, float fVeggieWeight);
    void ReceiveNewBerry(float fYumminessFactor, float fVitaminsPerGram);


    //multiple arrays of heterogeneous listeners
    list<ZFruitListener*> m_FruitListeners;
    list<ZVegetableListener*> m_VegetableListeners;
    list<ZBerryListener*> m_BerryListeners;
}


void ZDataManager::ReceiveNewFruit(ETexture eText, EColor eCol, ETaste eTaste)
{
   list<ZFruitListener*>::enumerator en = m_FruitListeners.GetEnumerator();
   while (en.MoveNext())
   {
      en.Current()->NewFruitReceived(eText, eCol, eTaste);
   }
}

//...here we have almost identical definitions for ReceiveNewVegetable and ReceiveNewBerry, but with different function signatures!!


Then we have the listener classes, doing the following:

ZFruitListener::Init() { 
  GetDataManager()->RegisterFruitListener(this);
}

ZFruitListener::~ZFruitListener() {
  GetDataManager().UnregisterFruitListener(this);
}


ZFruitListener::NewFruitReceived(ETexture, EColor, ETaste)
{
   //process new fruit
}


//same idea for ZVegetableListener / ZFruitListener Init, destructor, and receiving  functions.


As you can see here, I'm writing a lot of duplicate code, maintaining similar lists, registering classes in a similar way, and multicasting to different functions, but in a similar way. The main stumbling block here is the fact that the function signatures are different, making a more smooth solution elusive. 

2 comments:

  1. I posted a possible solution here:
    http://mortensjapan.blogspot.com/2010/11/coding.html

    ReplyDelete
  2. Phew you went all out :) I'll have a look thru soon, and will post my own solution, which is going in a slightly different direction.

    ReplyDelete