繁体   English   中英

如何优雅地声明变量集的子集

[英]How to elegantly declare subset of set of variables

让我们说要求是这样的:作为一个类用户,我想收集关于一个主题的信息,当类有足够的信息时,我希望一个类能够将收集的数据列表返回给我。 足够的信息被定义为 - 当收集来自所有可能信息的子集的所有信息时。 该子集不是固定的,而是提供给该类。

例如,这是所有可能信息的列表:

{
   string name; 
   int age;
   char sex;
   string location;
}

我想让我的用户有机会告诉我从一些数据源(我的班级解析数据)收听,直到我得到年龄和性别。

问题是我不知道如何在没有枚举的情况下传达它。 基本上我的枚举解决方案是监听数据源,直到我确定在我收集了所有数据的2组枚举(收集,必需)上使用std :: includes。

没有枚举可以做到吗?

每当我想要将一段逻辑的实现从它需要的地方解耦时 - 在这种情况下知道“有多少数据就足够了” - 我想到了一个回调函数。

据推测,您的班级可以收集的所有可能数据的集合是已知的(您的示例中的nameagesexlocation )。 这意味着你的班级的所有客户 (也可以)也知道它,而不会增加耦合和依赖的数量。

我的解决方案是创建一个封装此逻辑的“赋值器”类。 此类的子类的实例由客户端创建,并在初始数据请求时传递给数据收集器; 当收集到足够的数据时,该对象负责决定(并告诉“收集器”)。

#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;
};

在您的示例中,您希望特定客户能够指定agesex的组合就足够了。 所以你是这样的子类evaluator

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;
};

客户端在请求数据时传递此类的实例:

collector c;
c.collect_data( age_and_sex_required() );

age_and_sex_required实例报告收集的数据量“足够”并且您没有在collector类中构建任何逻辑,知识,枚举等时, collect_data方法退出并返回。 此外,构成“足够”的逻辑是无限可配置的 ,不会对collector类进行进一步的更改。

-----编辑-----

备用版本不会使用带有..._collected()方法的类,而只是使用单个(typedef'd)函数将接收collector作为参数接受并返回boolean

#include <functional>
typedef std::function< bool( collector const& ) > evaluator_t;

collector::collect_data(...)的代码只需调用即可

enough = e( *this );

每次收集一条数据。

这将消除对单独的必要性evaluator抽象接口,但将增加的依赖collector类本身,作为传递的对象evaluator_t功能将负责检查的状态collector对象,以评估足够的数据是否已收集(并要求collector有足够的公共接口来查询其状态):

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');
}

不确定这是否适合您,但由于每个项目可能存在或不存在,因此可以考虑使用boost::optional

{
   boost::optional<string> name; 
   boost::optional<int> age;
   boost::optional<char> sex;
   boost::optional<string> location;
}

然后,您的类可以使用bool validate()方法检查所需项集的存在。 这可以是类方法,也可以作为回调传入。

您可以默认为每个成员定义一个值“我是必需的”。

static const string required_name = /* your default name */;
// ...

您还可以使用整数作为位掩码,其行为类似于一组枚举。

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

易于使用且没有单个int开销:

  1. 不再required &= ~xx_flag值: required &= ~xx_flag
  2. 不再需要值: bool(required)
  3. 值是必需的: bool(required & xx_flag)
  4. ...

枚举似乎是最干净的方法,但我想如果您愿意,可以使用与每种数据类型对应的不同字符的短字符串。 这不是那么干净,但可能更容易调试。

难道你不能通过使用模板和抽象类来完成这样的行为,做这样的事情?

class SomeAbstract
{
public:
    virtual bool getRequired()  = 0;
    virtual void setRequired(bool req) = 0;
};

template <class T>
class SomeTemplate
{
    T value;
    bool required;

public:
    TemplateName(T t)
    {
        value = t;
        required = false;
    }
    void setRequired(bool req)
    {
        required = req;
    }
    bool getRequired()
    {
        return required;
    }
    void setValue(T newValue)
    {
        value = newValue;
    }
    T getValue()
    {
        return value;
    }
};

然后,您可以将属性列表声明为相同类型。

SomeTemplate<string> name; 
SomeTemplate<int> age;
SomeTemplate<char> sex;
SomeTemplate<string> location;

由于模板继承了相同的类型,因此您可以将它们存储在std::vector<SomeAbstract>并对它们进行相同的处理。

这不是经过测试的代码,这个想法可能有一些改进,但我希望你明白我的观点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM