首页 > 解决方案 > 树结构中的运行时元素替换

问题描述

我正在用 C++ 实现一个 REST API 客户端,它需要能够在运行时发现 API 的结构。REST API 的结构也可能在程序执行期间发生变化。API 模型基于 json 数组和对象。如果返回一个数组,则意味着检索到的元素是一个节点,如果返回一个对象,则意味着检索到的元素是一个叶子。

为了表示我正在建模的数据,我决定使用复合模式为用户提供树结构(节点或叶)的通用接口。

为了给我的模型提供一些 GUI 或 CLI 的钩子,我决定使用访问者模式来访问节点和叶子。

我也尝试使用访问者模式来实现模型的发现部分,但它显然不适合该任务,因为我需要将先前分配的派生类型的对象替换为另一种类型(例如:叶子变成节点)。在我看来,使用访问者模式是不可能的,因为对象不能替换自己。

我试图添加一些容器类来包装我的对象,但我最终遇到了类似的情况,我需要一个对象来替换自己。

我在网上阅读了有关设计模式的信息,试图找到一种可以帮助我的方法,但我没有找到任何合适的方法。

这是我到目前为止的代码 - 最相关的部分在最后(发现类):

#include <string>
#include <deque>
#include <stack>
#include <iostream>

class Visitor;

class Element{
public:
  Element(std::string name, Element* parent) : name{ name }, parent{ parent }{}
  std::string get_name(){
    return name;
  }
  std::string get_url(){
    std::stack<std::string> url_stack;
    Element* ancestor = parent;
    url_stack.push(name);
    while(ancestor != NULL){
      url_stack.push(ancestor->name);
      ancestor = ancestor->parent;
    }
    std::string url;
    while(!url_stack.empty()){
      url.append(url_stack.top());
      url_stack.pop();
    }
    return url;
  }
  Element* get_parent(void){
    return parent;
  }
  virtual ~Element(void){};
  virtual void accept(Visitor& visitor) = 0;
private:
  std::string name;
  Element* parent;
};

class ElementEmpty : public Element{
public:
  ElementEmpty(std::string name, Element* parent = NULL) : Element(name, parent){}
  virtual void accept(Visitor& visitor);
};

class ElementLeaf : public Element{
public:
  ElementLeaf(std::string name, std::string value, Element* parent = NULL) : Element(name, parent), value{ value }{}
  virtual void accept(Visitor& visitor);
  std::string value;
};

class ElementComposite : public Element{
public:
  ElementComposite(std::string name, std::deque<std::string> value_names, Element* parent = NULL) : Element(name, parent){
    for(auto& vn : value_names){
      values.emplace_back(new ElementEmpty(vn, this));
    }
  }
  ~ElementComposite(){
    while(!values.empty()){
      delete values.back();
      values.pop_back();
    }
  }
  virtual void accept(Visitor& visitor);
  std::deque<Element*> values;
};

class Visitor{
public:
  virtual void visit(ElementEmpty& empty) = 0;
  virtual void visit(ElementLeaf& leaf) = 0;
  virtual void visit(ElementComposite& composite) = 0;
};


void ElementEmpty::accept(Visitor& visitor){
  visitor.visit(*this);
}

void ElementLeaf::accept(Visitor& visitor){
  visitor.visit(*this);
}

void ElementComposite::accept(Visitor& visitor){
  visitor.visit(*this);
}

class Printer : public Visitor{
public:
  virtual void visit(ElementEmpty& empty){
    std::cout << empty.get_name() << " is empty" << std::endl;
  }
  virtual void visit(ElementLeaf& leaf){
    std::cout << leaf.get_name() << " is a leaf with value" << leaf.value << std::endl;
  }
  virtual void visit(ElementComposite& composite){
    std::cout << composite.get_name() << " is composite, visiting childrens" << std::endl;
    for(auto& v : composite.values){
      v->accept(*this);
    }
  }
};

class Discover : public Visitor{
public:
  virtual void visit(ElementEmpty& empty){
    discover(&empty);
  }
  virtual void visit(ElementLeaf& leaf){
    discover(&leaf);
  }
  virtual void visit(ElementComposite& composite){
    discover(&composite);
    for(auto& v : composite.values){
      v->accept(*this);
    }
  }
private:
  void discover(Element* element){
    std::string url = element->get_url();
    //HTTP GET (pseudo-code here to avoid adding irelevant code)
    json_val value = http_get(url);
    Element* new_element;
    if(value.is_array()){
      new_element = new ElementComposite(element->get_name(), value.as_array().as_deque(), element->get_parent());
    }
    else if(value.is_object()){
      new_element = new ElementLeaf(element->get_name(), value.as_string(), element->get_parent());
    }
    else{
      new_element = new ElementEmpty(element->get_name(), element->get_parent());
    }

    //TODO: How to set element to new_element? it's impossible since we are currently in element->accept()....
  }

};

有什么方法可以在运行时替换以前分配的对象,而不必添加指向已创建对象的指针数组并在需要时更改它们?

谢谢!

标签: c++restoopdesign-patterns

解决方案


对于您在提供的代码中分配的所有元素,您可以通过调用 new.

对于 discover() 可能被调用的所有元素,这是否也适用?

如果是这样,在某处调用的根源,将是该元素的真正所有者。例如,改变 discover() 的接口怎么样,让它返回一个指向新元素的指针,一直沿着调用链向下到达旧元素的真正所有者?然后该所有者可以删除旧元素,并开始将指向新分配的元素的指针存储在其位置。

当然,通常 - 不要使用原始指针,使用 std::unique_ptr 或 std::shared_ptr 或其他任何合适的东西,等等。但上述代码转换仍然可以使用这些。


推荐阅读