封装模板化字段访问器
Encapsulating templated field accessor
假设我们有以下内容:
class Item {
protected:
template <typename T>
Field<T>& getField(const String& fieldName);
template <typename T>
const Field<T>& getField(const String& fieldName) const;
};
template <typename T>
class Field {
public:
using data_t = T;
const String& getName() const;
data_t getValue() const;
void setValue(data_t&);
};
class File : public Item{
public: // There are also setters
String getFileName() const {
return getField<String>("file_name").getValue();
}
Integer getFileSize() const {
return getField<Integer>("file_size").getValue();
}
}
class Folder : public Item{
public:
String getFolderName() const {
return getField<String>("folder_name").getValue();
}
}
可以从 JSON 对象中的名称和类型映射字段:
String fileName = json.GetString("file_size");
file.setFileName(fileName);
Integer fileSize = json.GetInt("file_size");
file.setFileSize(fileSize);
我想将此模板简化为:
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
此外,任何人都可以定义一个虚拟结构来访问字段(并且可能使用错误的类型)。
现在,我的问题是:这个设计是个好主意吗?如果是这样,您将如何修复封装?
(我认为可以通过使用模板和继承在属性之间定义层次结构来修复封装。
因此 PropertyAccessor 只能访问由项的当前类型或其父项定义的属性,
而其他访问将是编译时错误)
编辑:Item
不仅仅是 JSON 对象的包装器,尽管 JSON 是字段值的可能来源之一(其他可能的来源包括 XML,从数据库加载等。)。更改了原始问题中的措辞以避免混淆。
为简单起见,Item::getField<T>(const String&)
由 map<String, unique_ptr<Field<T>>>
支持。
请注意,您没有提供 minimal, complete and verifiable example (尤其是:缺少 getField 的实现),因此很难给出一个好的答案;我还是根据你提供的信息试试。
编辑:完全修改了答案...
将您的第一种方法与第二种方法进行比较,同时尝试保留显式吸气剂(和 setters?),您正在引入新的内部 classes 和一个额外的模板 class.我个人的感觉是:你没有得到任何东西,反而让界面和 class 本身变得更加复杂和难以理解和使用(比较 file.getFileName()
和 file.get<File::file_name>()
),以及所有您描述的问题。
所以个人更愿意坚持第一种方法。使工作更简单的想法可能如下。我将原始字段重命名为数据,因为我更喜欢名称 'Field' 作为我使用它的目的。随意为两者找到更好的名字(如果你喜欢这个想法...):
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;
使数据和字段或至少字段(新字段)成为项目的内部 class 可能仍是一项改进。字段可能会被另一个提供 setter 的模板 class EditableField 继承,因此您可以根据使用的模板类型自由决定哪些字段可编辑或不可编辑。不过还没有想出后一个想法。
假设我们有以下内容:
class Item {
protected:
template <typename T>
Field<T>& getField(const String& fieldName);
template <typename T>
const Field<T>& getField(const String& fieldName) const;
};
template <typename T>
class Field {
public:
using data_t = T;
const String& getName() const;
data_t getValue() const;
void setValue(data_t&);
};
class File : public Item{
public: // There are also setters
String getFileName() const {
return getField<String>("file_name").getValue();
}
Integer getFileSize() const {
return getField<Integer>("file_size").getValue();
}
}
class Folder : public Item{
public:
String getFolderName() const {
return getField<String>("folder_name").getValue();
}
}
可以从 JSON 对象中的名称和类型映射字段:
String fileName = json.GetString("file_size");
file.setFileName(fileName);
Integer fileSize = json.GetInt("file_size");
file.setFileSize(fileSize);
我想将此模板简化为:
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
此外,任何人都可以定义一个虚拟结构来访问字段(并且可能使用错误的类型)。
现在,我的问题是:这个设计是个好主意吗?如果是这样,您将如何修复封装?
(我认为可以通过使用模板和继承在属性之间定义层次结构来修复封装。 因此 PropertyAccessor 只能访问由项的当前类型或其父项定义的属性, 而其他访问将是编译时错误)
编辑:Item
不仅仅是 JSON 对象的包装器,尽管 JSON 是字段值的可能来源之一(其他可能的来源包括 XML,从数据库加载等。)。更改了原始问题中的措辞以避免混淆。
为简单起见,Item::getField<T>(const String&)
由 map<String, unique_ptr<Field<T>>>
支持。
请注意,您没有提供 minimal, complete and verifiable example (尤其是:缺少 getField 的实现),因此很难给出一个好的答案;我还是根据你提供的信息试试。
编辑:完全修改了答案...
将您的第一种方法与第二种方法进行比较,同时尝试保留显式吸气剂(和 setters?),您正在引入新的内部 classes 和一个额外的模板 class.我个人的感觉是:你没有得到任何东西,反而让界面和 class 本身变得更加复杂和难以理解和使用(比较 file.getFileName()
和 file.get<File::file_name>()
),以及所有您描述的问题。
所以个人更愿意坚持第一种方法。使工作更简单的想法可能如下。我将原始字段重命名为数据,因为我更喜欢名称 'Field' 作为我使用它的目的。随意为两者找到更好的名字(如果你喜欢这个想法...):
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;
使数据和字段或至少字段(新字段)成为项目的内部 class 可能仍是一项改进。字段可能会被另一个提供 setter 的模板 class EditableField 继承,因此您可以根据使用的模板类型自由决定哪些字段可编辑或不可编辑。不过还没有想出后一个想法。