Archived

This topic is now archived and is closed to further replies.

Bagpuss

The Most efficient way to start this design

Recommended Posts

First of all, apologies, this is not focussed on game development, but I have found that there is a wealth of good advice in this forum, so if I can impose I''d like any thoughts on the project I have to work on below. I have to write an aplication to view files created using a certain transfer protocol. (so we can debug / check that some other software is producing the correct output). Looking at the specs, this protocol has 27 different data types. These 27 types are effectively variants on Byte, Float and char. Some of them are pretty basic like a 32 bit int (can treat as a 4 byte number), and string, but others have certain constraints attached (they might insist on only using certain characters, or a maximum number range) Would I be better defining these as 27 structs, and writing some supporting code to ensure that each is validated as it is assigned to the struct?, or creating 27 classes, allowing each class to ensure that as part of it''s constructor it can except if the data type is not correct ? Or would I be better off again storing them internally in a language type (probably C++), and just write 27 validation functions, and 27 conversion functions to make them look correct for display ? My last thought for doing it would be to make a template class, that will validate the type to be correct, and return a variable of the correct type, but as there are different rules for all the variations, I suspect that a template won''t really save me a great deal of work. Any ideas / comments most welcome here, as I want to make sure I start on this design only once ! Cheers, Bp

Share this post


Link to post
Share on other sites
Templates will save on typing for sure. But this problem is quite amenabdle to OOP, so better to use standard class hiearchy and inheritance as you get more band for your buck.

Here is a proposed design:



enum DATATYPE_SIG //all datatypes have unique sig defined here, not the invalid sig is important

{
SIG_INVALID,
SIG_CHAR,
SIG_FLOAT,
SIG_STRING_32_CHARS
};

struct Data_Type_Base
{
virtual DATATYPE_SIG get_type(){ return SIG_INVALID; } //defaults to invalid type


bool validate ( void )
{
get_validation_factory()->validate(this); //use singleton validation factory..

}

virtual bool seralize ( OutputStream& r_stream_out ); //inheritable seralzie functions

virtual bool seralize ( InputStream& r_stream_in ); //inheritable seralzie functions

}

struct Data_Type_Char : public Data_Type_Base
{
virtual DATATYPE_SIG get_type(){ return SIG_CHAR; } //each derived class must define their own get_type


virtual bool seralize ( OutputStream& r_stream_out );
virtual bool seralize ( InputStream& r_stream_in );

unsigned char m_element;
}

struct Data_Validator_Base //base class for double dispatching note all the elements must have a validate_data func

{
virtual bool validate_data ( Data_Type_Base* p_data){ return false; }
virtual bool validate_data ( Data_Type_Char* p_data){ return false; }
};

struct Data_Validator_Char : public Data_Validator_Base
{
virtual bool validate_data ( Data_Type_Char* p_data ) //only defines funciton which it is intreseted in

{
//simple test for 0 element value to be invlaid, you would ofcoruse have your own, this is just an example


if (p_data->m_element==0)
return false;

return true;
}
};

class Validation_Factory
{
public:

bool validate ( Data_Type_Base* p_data )
{
assert(p_data!=NULL);

DATATYPE_SIG sig = p_data->get_type();

if (m_validators.find(sig) == m_validators.end())
{
assert(0);
return false;
}

return m_validators[sig]->validate_data( p_data ); //visitor pattern to dispatch to the correct validator

}

private:

Validation_Factory(); //constructor is private only can be created by friend function


std::map<DATATYPE_SIG,Data_Validator_Base*> m_validators; //map of type->validators, loose coupling allows for quickly changing valditoar to data type


friend Validation_Factory* get_validation_factory(void);
};

Validation_Factory* get_validation_factory(void) //singleton pattern

{
static Validation_Factory factory;
return &factory;
}



There ya go, ive noted alot of patterns im using. The main strengths of this design is the loose coupling of validator to datatype. Also the use of the vistor pattern to correctly dispatch the approraite datatype to the validator without using static cast.

BTW : anyone know what the tags are for making a source block?

-ddn


[edited by - ddn3 on October 9, 2003 9:23:59 PM]

[edited by - ddn3 on October 9, 2003 9:25:21 PM]

[edited by - ddn3 on October 9, 2003 9:26:54 PM]

[edited by - ddn3 on October 9, 2003 9:35:11 PM]

Share this post


Link to post
Share on other sites
Thanks, sorry for not posting sooner, been struggling with my network connection.

So using your code, all I need to do is add code to create an instance of each of my 27 types to be validated, and stick them all in the Map ?

Thanks again,

Bp

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Yes, just define an interface for adding validator objects into the valadaiton factory. I would let the factory own them, so when the factory dies, it deletes the valadators too.

Hope it works out for you.

-ddn

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
To clarify, you need to :

-add the additioanl enums
-add the dervied data types with the approrirate functions
-add into the validator base the approriate dispatch function
-add the derived validator types
-add an interface to the validator factory to register validators.

and that should be it. Its alot of typing, but 27 unique data types is alot. I tried to make it more managble by automating the dispatching and rewritting redundant code. You just need to code up the verify function, and seralization functions.

It might be possible to code up a template to do most of this for you and you can pass into the template a function which is both verify/seralize.

Good Luck!

-ddn

Share this post


Link to post
Share on other sites
OK, I have written it as I think it ought to be written,
but when I attempt to set up the map, I have the line

m_validators.insert(DIC_UL,mUL); (mUL is an istance of the DIC_UL validator type)
and I get an error claiming that DIC_UL cannot be converted into std::_tree<>::Iterator.

I assumed (obviously wrongly) that as an enum was basically treated as an int, that there wouldn''t be a problem with this. So te next question is, how do I get round it ?

Bp


Share this post


Link to post
Share on other sites
Guest Anonymous Poster
enums are treated differently between different compilers. I know in MSVC 6.0 it treats enums as ints, but in C# enums are their own type and can''t be converted to ints without static casts.

But thats not what is happening here. The insert function for a map has serveral forms.

The form your using expects a range, so its a map::iterator begin -> end.

If you want to insert a single element into the map using the insert function it shoudl be

typedef std:air<DATATYPE_SIG,Data_Validator_Base*> _Validator_Pair;

m_validators.insert(_Validator_Pair(DIC_UL,mUL));

i typedef the validator pair so its easier to read but you dont have too..

or you can use the more convient, but slightly less direct form :

m_validators[DIC_UL] = mUl;

which does assignment and creation.

mUL should be a ptr to a validator object, btw.

-ddn

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
OK, I have just started to add in my validation code into my funciton stubs, but whenever I run it, no matter what the datatype, and which validator the pointer points to, the software always uses the Data_Validator_Base() function, and so returns false.

As I understood it, if a container was made up of objects that inherited from a base, then the correct object instance would be called, not the base one? This doesn''t seem to be hapenning.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Ill look into it. According to the c++ standards it should use the best matching function to call. But it might be be the case that when the ptr is passed into the valaidation factory it is now a base ptr. Hmm..

-ddn

Share this post


Link to post
Share on other sites
Yes the problem is when the object is passed into the validation factory its now a base ptrs. So a few methods to resovle this.

You can use casting to the approraite type or you can use double dispathing. You could create a type factroy but that will use templates and im not even sure how to do that So.

The easiest is to use the double dispatching method.

Youll need to add this into the datatype classes a new function called validate_indirect. Its purpose solely is to recover the type data. Its confusing at first, but you must redfeine this for all dervied child classes, even though the impl doesnt change.

BTW you cant define the funciton impl within the class defintion becuase of a cyclic dependency between valaditaor_base and datatype now.

So predeclare the calss Data_Validator within the header of the datatypes.

And define the functions within the cpp. I impl them iwthin the header for clarity sake.

Oh also ive included the changes the valaidation factory valdiate method. Note its now calling the valdiate indirect function.


struct Datatype_Base
{
virtual DATATYPE_SIG get_type(){ return SIG_INVALID; }

bool validate ( void )
{
get_validation_factory()->validate(this);
}

virtual bool validate_indirect ( Data_Validator_Base* p_validator ) //Add this function to the base class

{
return p_validator->validate( this );
}

virtual bool seralize ( OutputStream& r_stream_out );
virtual bool seralize ( InputStream& r_stream_in );
};

struct Data_Type_Char : public Data_Type_Base
{
virtual DATATYPE_SIG get_type(){ return SIG_CHAR; }

virtual bool validate_indirect ( Data_Validator_Base* p_validator ) //Redefine this function for all sub classes, its impl doesnt change

//but it passes the correct type to the validator, note cannot inherit from multiple derived data types then..

{
return p_validator->validate( this );
}

virtual bool seralize ( OutputStream& r_stream_out );
virtual bool seralize ( InputStream& r_stream_in );
};


bool Validation_Factory::validate ( Data_Type_Base* p_data )
{
assert(p_data!=NULL);

DATATYPE_SIG sig = p_data->get_type();

if (m_validators.find(sig) == m_validators.end())
{
assert(0);
return false;
}

return p_data->validate_indirect(m_validators[sig]); //visitor pattern using double dispatch to the correct validator func

}


Ive tested this on my side and its all good to go. Sorry for the trouble.

Good Luck!

-ddn

[edited by - ddn3 on October 17, 2003 3:02:39 PM]

[edited by - ddn3 on October 17, 2003 3:05:58 PM]

Share this post


Link to post
Share on other sites