lets say that requirement is this: As a class user I would like to collect information about a subject and when class has enough information I would like for a class to return to me the list of collected data. Enough information is defined as - when all information from subset of all possible information is collected. That subset is not fixed and it is provided to the class.
For example this is list of all possible information:
{
string name;
int age;
char sex;
string location;
}
and I want to give my users a possibility to tell me to listen from some data source(from which my class parses data) until I get the age and sex.
Problem is that I dont know how to convey that without an enum. Basically my enum solution is listening to data source until I determine using using std::includes on 2 set of enums(collected, required) that I have collected all the data.
Is it possible to do it without enums?
Whenever I want to decouple the implementation of a piece of logic from where it's needed - in this case the knowledge of "how much data is enough" - I think of a callback function.
Presumably the set of all possible data that your class can collect is known (name
, age
, sex
& location
in your example). This means all clients of your class (can) know about it also, without increasing the amount of coupling & dependence.
My solution is to create an "evaluator" class that encapsulates this logic. An instance of a subclass of this class is created by the client and handed to the data collector at the time of the initial request for data; this object is responsible for deciding (and telling 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 particular client to be able to specify that the combination of age
and sex
is sufficient. So you subclass evaluator
like so:
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 quits and returns when the age_and_sex_required
instance reports that the amount of data collected is "enough", and you haven't built any logic, knowledge, enums, whatever, into the collector
class. Also, the logic of what constitutes "enough" is infinitely configurable with no further changes to the collector
class.
----- EDIT -----
An alternate version would not use a class with the ..._collected()
methods but simply a single (typedef'd) function that accepts the collector
as a parameter and returns boolean
:
#include <functional>
typedef std::function< bool( collector const& ) > evaluator_t;
The code in collector::collect_data(...)
would simply call
enough = e( *this );
each time a piece of data is collected.
This would eliminate the necessity of the separate evaluator
abstract interface, but would add dependence on the collector
class itself, as the object passed as the evaluator_t
function would be responsible for checking the state of the collector
object to evaluate whether enough data has been collected (and would require the collector
to have sufficient public interface to inquire about its state):
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');
}
Not sure if this will work for you or not, but because each item may or may not be present, boost::optional
sprung to mind.
{
boost::optional<string> name;
boost::optional<int> age;
boost::optional<char> sex;
boost::optional<string> location;
}
Your class could then have a bool validate()
method which checks the presence of the required set of items. This could either be a class method, or passed in as a callback.
You can define a value by default for each member saying "i'm required".
static const string required_name = /* your default name */;
// ...
You can also use an integer as a bitmask which will behave like a set of enums.
typedef int mask_type;
static const mask_type name_flag = 0x01;
static const mask_type age_flag = 0x02;
static const mask_type sex_flag = 0x04;
static const mask_type location_flag = 0x08;
//...
mask_type required = name_flag | age_flag; // need to collect name & age
collect(&my_instance, required) // collect and set required values
Easy to use and no overhead than a single int
:
required &= ~xx_flag
bool(required)
bool(required & xx_flag)
Enums seems the cleanest way to do this, but I suppose if you preferred you could use short strings with a different character corresponding to each type of data. That's not so clean but might be easier to debug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With