Whenever I want to separate the implementation of logic from where it is needed - in this case, knowing “how much data is enough” - I think of the callback function.
Presumably, you know a lot of all the possible data that your class can collect ( name , age , sex and location in your example). This means that all clients of your class (may) also know this without increasing the number of connections and dependencies.
My solution is to create an "evaluator" class that encapsulates this logic. An instance of the subclass of this class is created by the client and passed to the data collector during the initial data request; this object is responsible for making the decision (and the message to the "collector") when enough data has been collected.
#include <string> // The class that decides when enough data has been collected // (Provided to class "collector" by its clients) class evaluator { public: virtual ~evaluator() {}; // Notification callbacks; Returning *this aids in chaining virtual evaluator& name_collected() { return *this; } virtual evaluator& age_collected() { return *this; } virtual evaluator& sex_collected() { return *this; } virtual evaluator& location_collected() { return *this; } // Returns true when sufficient data has been collected virtual bool enough() = 0; }; // The class that collects all the data class collector { public: void collect_data( evaluator& e ) { bool enough = false; while ( !enough ) { // Listen to data source... // When data comes in... if ( /* data is name */ ) { name = /* store data */ enough = e.name_collected().enough(); } else if ( /* data is age */ ) { age = /* store data */ enough = e.age_collected().enough(); } /* etc. */ } } // Data to collect std::string name; int age; char sex; std::string location; };
In your example, you wanted a specific client to be able to indicate that a combination of age and sex is sufficient. So, you subclass evaluator as follows:
class age_and_sex_required : public evaluator { public: age_and_sex_required() : got_age( false ) , got_sex( false ) { } virtual age_and_sex_required& age_collected() override { got_age = true; return *this; } virtual age_and_sex_required& sex_collected() override { got_sex = true; return *this; } virtual bool enough() override { return got_age && got_sex; } private: bool got_age; bool got_sex; };
The client passes an instance of this class when requesting data:
collector c; c.collect_data( age_and_sex_required() );
The collect_data method exits and returns when an instance of age_and_sex_required reports that the amount of data collected is "sufficient" and you have not created any logic, knowledge, enumerations, etc. in the collector class. In addition, the logic of what is “enough” is infinitely configurable without further changes to the collector class.
----- EDIT -----
The alternative version will not use a class with methods ..._collected() , but just one (typedef'd) function that takes collector as a parameter and returns boolean :
#include <functional> typedef std::function< bool( collector const& ) > evaluator_t;
The code in collector::collect_data(...) will just call
enough = e( *this );
every time a piece of data is collected.
This would eliminate the need for a separate abstract evaluator interface, but add a dependency on the collector class itself, since the object passed as the evaluator_t function would be responsible for checking the state of the collector to evaluate if enough data had been collected (and would require the collector have enough public interface to find out about its status):
bool age_and_sex_required( collector const& c ) { // Assuming "age" and "sex" are initialized to -1 and 'X' to indicate "empty" // (This could be improved by changing the members of "collector" to use // boost::optional<int>, boost::optional<char>, etc.) return (c.age >= 0) && (c.sex != 'X'); }