在运行时将属性与类实例相关联

时间:2014-09-17 21:14:02

标签: c++ oop design-patterns

是否有一种惯用的C ++方法可以将属性与一组固定的类实例动态关联?

假设我们有一个Element类。每个元素总是具有成员变量包含的某些属性。

struct Element {
    unsigned atomic_protons;
    float mass;
};

我们可能会将其他属性与每个元素相关联,但并非每个使用Element类的程序都会对相同的属性感兴趣。也许有时我们对品味感兴趣,有时候我们对颜色感兴趣,代表这些属性的变量初始化可能会很昂贵。也许我们甚至不知道运行时我们想要什么属性。

我想到的解决方案是一组并行数组。一个数组包含实例本身,该数组的索引隐式地将每个实例与一系列“并行”数组中的项相关联。

// fixed set of Element instances
std::vector<Element> elements;
// dynamic properties
std::vector<Flavor> element_flavors;
std::vector<Color> element_colors;

根据需要创建每个属性向量。

这个解决方案没问题,但完全不像惯用的C ++。除了美学外,这种安排使得从给定的Element实例中查找属性变得尴尬。我们需要在每个Element实例中插入一个数组索引。此外,每个向量中的大小信息都是多余的。

如果我们对给定属性的所有值感兴趣,那么数据就会被正确排列。但通常情况下,我们希望走向相反的方向。

只要在每次添加新属性时都不需要修改类,那么以某种方式修改Element类的解决方案就可以了。还假设存在使用Element类的方法,所有程序共享,我们不希望这些方法中断。

2 个答案:

答案 0 :(得分:2)

所以,有两种情况。

您可以以静态方式将属性附加到程序。但是在编译之前必须知道这个属性。是的,有这样的习惯方式。它被称为专业化,派生或继承:

struct ProgramASpecificElement : Element 
{
   int someNewProperty;
};

第二种情况更有趣。如果要在运行时添加属性。然后你可以使用map,如下所示:

std::unordered_map<Element*, int> elementNewProperties;

Element a;
elementNewProperties[&a] = 7;
cout << "New property of a is: " << elementNewProperties[&a];

如果您不想为在地图中搜索而支付性能损失,那么您可以在Element中预测它可能具有新属性:

struct Property { 
   virtual ~Property() {}
};
template <typename T>
struct SimpleProperty : Property {
     T value;
};

struct Elememt {
  // fixed properties, i.e. member variables
  // ,,,
  std::unordered_map<std::string, Property*> runtimeProperties;
};

 Element a;
 a.runtimeProperties["age"] = new SimpleProperty<int>{ 7 };
 cout << "Age: " << *dynamic_cast<SimpleProperty<int>*>(a.runtimeProperties["age"]);

当然,上面的代码没有任何必要的验证和封装 - 只是一些例子。

答案 1 :(得分:2)

我认为PiotrNycz建议的std::unordered_map<Element*, Flavor>解决方案是将Flavor与特定Element相关联的完美“惯用”方式,但我想提出一个替代方案。

修复您想要在Element上执行的操作,您可以提取出一个界面:

class IElement {
 public:
  virtual ~IElement() {}
  virtual void someOperation() = 0;
};

然后,您可以轻松存储IElement指针(最好是智能指针)的集合,然后根据需要进行专门化。具有不同行为且包含不同属性的不同特化。你可以有一个工厂决定在运行时创建哪个特化:

std::unique_ptr<IElement>
elementFactory(unsigned protons, float mass, std::string flavor) {

  if (!flavor.isEmpty())  // Create specialized Flavored Element
    return std::make_unique<FlavoredElement>(protons, mass, std::move(flavor));

  // Create other specializations...

  return std::make_unique<Element>(protons, mass);  // Create normal element
}

您的案例中的问题是您可以轻松获得大量专业化:ElementFlavoredElementColoredElementFlavoredColoredElementTexturedFlavoredElement等。 ..

在这种情况下适用的一种模式是Decorator pattern。您使FlavoredElement成为包装IElement的装饰器,但也实现了IElement接口。然后,您可以选择在运行时向元素添加flavor:

class Element : public IElement {
private:
  unsigned atomic_protons_;
  float    mass_;
public:
  Element(unsigned protons, float mass) : atomic_protons_(protons), mass_(mass) {}
  void someOperation() override { /* do normal thing Elements do... */ }
};

class FlavoredElement : public IElement {
private:
  std::unique_ptr<IElement> element_;
  std::string flavor_;
public:
  FlavoredElement(std::unique_ptr<IElement> &&element, std::string flavor) :
    element_(std::move(element)), flavor_(std::move(flavor)) {}
  void someOperation() override {
    // do special thing Flavored Elements do...
    element_->someOperation();
  }
};

class ColoredElement : public IElement {
private:
  std::unique_ptr<IElement> element_;
  std::string color_;
public:
  ColoredElement(std::unique_ptr<IElement> &&element, std::string color) :
    element_(std::move(element)), color_(std::move(color)) {}
  void someOperation() override {
    // do special thing Colored Elements do...
    element_->someOperation();
  }
};

int main() {
  auto carbon = std::make_unique<Element>(6u, 12.0f);
  auto polonium = std::make_unique<Element>(84u, 209.0f);
  auto strawberry_polonium = std::make_unique<FlavoredElement>(std::move(polonium), "strawberry");
  auto pink_strawberry_polonium = std::make_unique<ColoredElement>(std::move(strawberry_polonium), "pink");

  std::vector<std::unique_ptr<IElement>> elements;
  elements.push_back(std::move(carbon));
  elements.push_back(std::move(pink_strawberry_polonium));

  for (auto& element : elements)
    element->someOperation();
}