Reputation: 6348
I'll be writing a validator for a specific file format (the format itself is unimportant). There's a large set of documents that specify what every section of the format needs to look like, how the different parts relate and so on. Lots and lots of MUST's, SHALL's, SHOULD's, MAY's etc.
The architecture I envision is as follows: load the document into memory/separate files on disk, and then run numerous validation "passes" on the document: every pass would check for the adherence to one and only one rule specified in the standard, and if the pass fails, an error message is printed to stdout. Every pass would be a separate class implementing a common interface and an instance of each pass would be created on the stack, run on the document and the Result collected (containing error ID, message, line/column number etc). The Results would then be looped over and messages printed out. A unit test could then be easily created for every pass.
Now, I expect to eventually have hundreds of these "pass" classes. And every one of these would need to be instantiated once, run over the document, and the Result collected.
Do you see where I'm going with this? How do I create all these different instances without having a 500 line function that creates each and every one, line by line? I'd like some sort of loop. I'd also like the new pass classes to somehow be "discovered" when created, so I don't have to manually add lines that instantiate those new classes.
Thinking about it now, all this seems to remind me of unit testing frameworks...
Upvotes: 2
Views: 757
Reputation: 84812
C++ is a static language that lacks reflection, hence you will have to enumerate your classes one way or another, be it a map of class names to factory functions, an explicit function that creates them all, or cppunit-style "registration" (same as putting them in a static map).
With that said, go with the simplest possible way that's easiest to change, and does not introduce too much needless work and boilerplate code: creating a list of them in a function.
You are saying you will have hundreds of them "eventually", not now. By the time you have hundreds of them, your design will have changed completely. If instantiation of all of them is localised in one function, it will make it easier to change your (uniform) design to add validators that depends on the state of previous validators, add arguments to validator constructors, or replace a simple list of validators with some composite structure, like conditional validators (e.g. run X only if Y had certain result, or run Z if X and Y are enabled).
Simple code like that is also easier to throw away if it is no longer fit for the task, as you only tend to get invested in a complicated designs and add kludges to it instead of throwing it away. :)
When your codebase matures, you will know exactly what kind of arrangement you need, but for now, do the simplest thing that can possibly work.
PS
Use a file-local macro, if typing some_ptr<Foo> foo = new Foo()
bothers you, macros aren't that dirty :)
E. g.
#define NEW_V(cls, ...) do {\
std::tr1::shared_ptr<Validator> v(new cls(__VA_ARGS__));\
lst.push_back(v);\
} while(0)
...
std::list< std::tr1::shared_ptr<Validator> >
CreateValidators() {
std::list< std::tr1::shared_ptr<Validator> > lst;
CREATE_V(Foo);
CREATE_V(Bar);
CREATE_V(Baz, "baz");
return lst;
}
Upvotes: 1
Reputation: 36082
I think it sounds like a job for a parser e.g. ANTLR creating a grammar to do the checking for you. It can create a parser in C code which could use to check the syntax. That way you will also be pretty flexible for future changes of the document.
Upvotes: 0
Reputation: 49156
For something with this many classes, I'd go with a registry that allows you to instantiate all registered classes. I wrote some sample code that shows a factory function and the registration pattern (you'd need to add the ability to iterate over the registered functions).
The nice part about this is that there's a registration done with the class definition, but no other code in the system directly references your class. It's a nice decoupling.
The only trouble with the registration pattern is that the compiler sometimes gets too smart and removes code it thinks is unused. Solutions for that vary.
Upvotes: 1
Reputation: 19928
I would manage a list of possible parser classes (by name for example) and use a factory pattern to create the concrete implementation. The parser classes shall have a common interface to enable you to use the same code to hand over information and process the data.
The key to success is to manipulate the parsers over the interface only and delegage the parser creation to one factory class (only this one knows about the concrete implementation and yields pointers to an interface). This way you could activate and deactivate rules by adding or removing parsers from the list of parsers to build.
Instead of making passes one after the other you could also do it chunkwise: reading a few lines from the input and then hand over that to all parsers.
Upvotes: 0