Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to elegantly declare subset of set of variables

Tags:

c++

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?

like image 662
NoSenseEtAl Avatar asked Feb 16 '14 00:02

NoSenseEtAl


4 Answers

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');
}
like image 88
aldo Avatar answered Sep 30 '22 13:09

aldo


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.

like image 32
Roddy Avatar answered Sep 30 '22 12:09

Roddy


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 :

  1. value is not required anymore : required &= ~xx_flag
  2. no more values required : bool(required)
  3. value is required : bool(required & xx_flag)
  4. ...
like image 22
Kiwi Avatar answered Sep 30 '22 12:09

Kiwi


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.

like image 20
Tim Bergel Avatar answered Sep 30 '22 13:09

Tim Bergel