封装模板化字段访问器

时间:2016-07-14 04:12:22

标签: c++ oop templates encapsulation

假设我们有以下内容:

String fileName = json.GetString("file_size");
file.setFileName(fileName);
Integer fileSize = json.GetInt("file_size");
file.setFileSize(fileSize);

可以从JSON对象中的名称和类型映射字段:

parse<File::file_name>(json, file)
parse<File::file_size>(json, file)

我想将这个锅炉板简化为:

class File : public Mixin<Item, PropertyAccessor> {
public:
    struct file_name { // Generated with a macro
        using type = String;
        static const String& getFieldName() {
            static String fieldName = "file_name"
            return fieldName;
        }
    }

    struct file_size {
        using type = Integer;
        static const String& getFieldName() {
            static String fieldName = "file_size"
            return fieldName;
        }
    }
}

class PropertyAccessor {
    template <typename Property>
    typename Property::type get() const; // Arcane inside

    template <typename Property>
    void set(typename Property::type&);
}

换句话说,让编译器根据其名称(作为类型)计算出字段的类型。

所以我做了以下事情:

auto fileName = file.get<file_name>();  // fileName is String
file.set<file_size>(5);                 // Works as long as 5 is convertible to Integer

好消息是可以做到以下几点:

file.get<folder_name>(); // Guaranteed tragedy if implementation is left as is

然而,问题是封装破坏了:

Item

此外,任何人都可以定义一个虚拟结构来访问字段(并且可能使用错误的类型)。

现在,我的问题是:这个设计是个好主意吗?如果是这样,你将如何修复封装?

(我认为可以通过使用模板和继承在属性之间定义层次结构来修复封装。 因此,PropertyAccessor只能访问项目当前类型或其父项定义的属性, 而其他访问将是编译时错误)

编辑:Item::getField<T>(const String&)不仅仅是JSON对象的包装器,尽管JSON是字段值的可能来源之一(其他可能的源包括XML,从数据库加载等)。改变原问题中的措辞以避免混淆。

为简单起见,map<String, unique_ptr<Field<T>>>match (chatitems) with chatitems as C match (teams) with distinct teams as T match (T)-[r:PartOf]-(C) with C, T.id as num order by num desc return {chatid:C.id, teams:collect(num)[0..10]} 支持。

1 个答案:

答案 0 :(得分:0)

请注意,您没有提供minimal, complete and verifiable example(特别是:缺少getField的实现),因此很难给出一个好的答案;我仍然试着你提供的信息。

编辑:完全重写了答案......

将第一种方法与第二种方法进行比较,在尝试省略显式getter(和setter?)时,您将引入新的内部类和一个额外的模板类。我个人对此感觉:你没有获得任何东西,但是使界面和类本身更复杂,更难以理解和使用(比较file.getFileName()file.get<File::file_name>()),以及你描述的所有问题

所以个人宁愿坚持第一种方法。使工作更简单的想法可能如下。我将原始字段重命名为数据,因为我更喜欢名称“字段”用于我使用它。随意为两者找到更好的名字(如果你喜欢这个想法......):

template < typename T >
class Data
{
public:
    using data_t = T;
    const String& getName() const;
    data_t getValue() const;
    void setValue(data_t&);
};

class Item
{
private:
    template < typename T >
    friend class Field;
    template <typename T>
    Data<T>& getField(const String& fieldName);
    template <typename T>
    const Data<T>& getField(const String& fieldName) const;
};

template < typename T >
class Field
{
    Item& parent;
    String name;
public:
    Field(Item& parent, String const& name)
            : parent(parent), name(name)
    { }
    String const& getName() const
    {
        return name;
    }
    T getValue() const
    {
        return parent.getField<T>(name).getValue();
    }
    operator T() const
    {
        return getValue();
    }
};
class File : public Item
{
public:
    Field<String> name;
    Field<Integer> size;

    File()
        : Item(),
          name(*this, "file_name"),
          size(*this, "file_size")
    { }
};
class Folder : public Item
{
public:
    Field<String> name;

    Folder()
        : Item();
          name(*this, "folder_name")
    { }
};

然后可以像这样使用:

File file;
Folder folder;
String s(file.name);
Integer i(file.size);
s = folder.name;

无论是数据还是字段,还是至少Field(新的)都是Item的内部类,可能还需要改进。字段可能由提供setter的另一个模板类EditableField继承,因此您可以根据使用的模板类型自由决定哪些字段是可编辑的。但是,还没有解决后一个想法。