I l@ve RuBoard | ![]() ![]() |
Solution![]() Let's consider the questions one by one.
GenericTableAlgorithm can be substantially improved because it currently holds two jobs. Just as humans get stressed when they have to hold two jobs—because that means they're loaded up with extra and competing responsibilities—so too this class could benefit from adjusting its focus. In the original version, GenericTableAlgorithm is burdened with two different and unrelated responsibilities that can be effectively separated, because the two responsibilities are to support entirely different audiences. In short, they are:
Guideline
That said, let's look at some improved code: //--------------------------------------------------- // File gta.h //--------------------------------------------------- // Responsibility #1: Providing a public interface // that encapsulates common functionality as a // template method. This has nothing to do with // inheritance relationships, and can be nicely // isolated to stand on its own in a better-focused // class. The target audience is external users of // GenericTableAlgorithm. // class GTAClient; class GenericTableAlgorithm { public: // Constructor now takes a concrete implementation // object. // GenericTableAlgorithm( const string& table, GTAClient& worker ); // Since we've separated away the inheritance // relationships, the destructor doesn't need to be // virtual. // ~GenericTableAlgorithm(); bool Process(); // unchanged private: struct GenericTableAlgorithmImpl* pimpl_; // MYOB }; //--------------------------------------------------- // File gtaclient.h //--------------------------------------------------- // Responsibility #2: Providing an abstract interface // for extensibility. This is an implementation // detail of GenericTableAlgorithm that has nothing // to do with its external clients, and can be nicely // separated out into a better-focused abstract // protocol class. The target audience is writers of // concrete "implementation detail" classes which // work with (and extend) GenericTableAlgorithm. // class GTAClient { public: virtual ~GTAClient() =0; virtual bool Filter( const Record& ); virtual bool ProcessRow( const PrimaryKey& ) =0; }; //--------------------------------------------------- // File gtaclient.cpp //--------------------------------------------------- bool GTAClient::Filter( const Record& ) { return true; } As shown, these two classes should appear in separate header files. With these changes, how does this now look to the client code? The answer is: pretty much the same. class MyWorker : public GTAClient { // ... override Filter() and ProcessRow() to // implement a specific operation ... }; int main() { GenericTableAlgorithm a( "Customer", MyWorker() ); a.Process(); } While this may look pretty much the same, consider three important effects.
Remember the computer science motto: Most problems can be solved by adding a level of indirection. Of course, it's wise to temper this with Occam's Razor: Don't make things more complex than necessary. A proper balance between the two in this case delivers much better reusability and maintainability at little or no cost—a good deal by all accounts. Let's talk about more generic genericity for a moment. You may have noticed that GenericTableAlgorithm could actually be a function instead of a class (in fact, some people might be tempted to rename Process() as operator()(), because now the class apparently really is just a functor). The reason it could be replaced with a function is that the description doesn't say that it needs to keep state across calls to Process(). For example, if it does not need to keep state across invocations, we could replace it with: bool GenericTableAlgorithm( const string& table, GTAClient& method ) { // ... original contents of Process() go here ... } int main() { GenericTableAlgorithm( "Customer", MyWorker() ); } What we've really got here is a generic function, which can be given "specialized" behaviour as needed. If you know that method objects never need to store state (that is, all instances are functionally equivalent and provide only the virtual functions), you can get fancy and make method a nonclass template parameter instead. template<typename GTACworker> bool GenericTableAlgorithm( const string& table ) { // ... original contents of Process() go here ... } int main() { GenericTableAlgorithm<MyWorker>( "Customer" ); } I don't think that this buys you much here besides getting rid of a comma in the client code, so the first function is better. It's always good to resist the temptation to write cute code for its own sake. At any rate, whether to use a function or a class in a given situation can depend on what you're trying to achieve, but in this case writing a generic function may be a better solution. ![]() |
I l@ve RuBoard | ![]() ![]() |