键入安全的实体对象,其中包含大量数据
type-safe Entity Objects with heavy data
目前,我们的系统使用类似于Java实体bean的东西,尽管它是用C++/SQL编写的。本质上,有些类(或多或少)象征着表,这些类的实例相当于表行。我想补充一点,这种方法一开始就有缺陷,看看这篇著名的文章:http://blogs.tedneward.com/2006/06/26/The+越南+Of+计算机+科学.aspx另一方面,它运行良好,只要你接受它会导致一些不纯洁的东西,有时会有点黑客攻击。
尽管如此,实际的问题如下:虽然这些实体中的许多实体的内存占用相对较轻(十几列包含int、float和字符串),并且具有良好的性能,但其中一些实体实际上并非如此。
- 有些包含二进制斑点,如网格或图片。有人可能会说,一开始就不应该将这些存储在DB中,但这是另一个主题
- 有些实际上并不包含很多数据(以字节为单位),但由于涉及的联接数量,获取完整集是一个非常大且相当慢的查询
转折点:这些"胖"对象通常在没有完整数据的情况下使用。想象一下,您有一个"Passport"类,其中包含生物特征数据、家庭关系树,以及姓名和出生日期。如果你想显示护照列表,你只需要基本数据。
我目前正在做的是创建一个Passport实例,但需要两个步骤来填充它。第一步只添加简单字段,但保留重字段(为NULL)。该实例稍后可以传递给一个函数,该函数将添加所有困难的字段。只要我不犯错误,并在需要"完整"版本的地方使用"浅"实例,这就可以顺利工作。当然,我可以添加各种内部检查,但这不仅规模很小(或多或少为每个实体重新实现它们),而且非常容易出错。
因此,我的问题是:我想在编译时区分这两个版本,而不仅仅是在运行时。这样,我会在大多数错误发生之前发现它们。
唯一可行的想法是将这两个部分分成两半,并将其作为元组传递。如果缺少第二个元组,那么显然还没有加载胖数据。虽然这是有效的,但它导致了卑鄙的语法:
std::vector< EntityTuple<EmptyPassport, FullPassport>>
我得到的所有类型安全性都是以可读性为代价的,这并不是一个很大的改进。目前,我没有更好的想法,我怀疑这在C++中实际上是不可能的,但我可能错了。非C++的建议也很受欢迎,未来可能会有更好的方法。当然,如果有人能很好地说明为什么这是不可能的,我也会接受。
概述
让我提出一些处理"重"属性的想法,如Blob、图像、文件。请记住,不存在"一个适用于所有人的解决方案"。我个人拒绝"加载所有重属性"标志的想法,&提出其他想法。
之前,我继续,请忽略小的语法或逻辑错误,并将重点放在代码的逻辑示例上。
[1]定义示例
首先,让我们从一个简单的例子开始:
public class EmployeeClass
{
public:
int Key;
char FirstName[150];
char LastName[150];
Image* Photo; // <- picture
Blob* Contract; // <- scanned contract
}; // class
首先,你不必加载或不加载"重"属性,因为"实体"模型或其他编程技术是这样说的
事实上,我不会为重对象添加标志,因为这意味着加载所有"重"属性,或者根本不加载任何"重"特性。而且,有时你可能需要加载其中的一些,但不是全部。
[2]加载属性
通常,程序的逻辑指示何时加载属性。
常见的做法是针对每种情况使用不同的构造函数:
public class EmployeeClass
{
public:
int Key;
char FirstName[150];
char LastName[150];
Image* Photo;
Blob* Contract;
public:
// --> generic constructor
EmployeeClass()
{
Key = 0;
strcpy(FirstName, "");
strcpy(LastName, "");
Photo = null;
Contract = null;
} // EmployeeClass()
// --> "light" constructor
EmployeeClass
(
int AKey,
char* AFirstName,
char* ALastName
)
{
Key = AKey;
strcpy(FirstName, AFirstName;
strcpy(LastName, ALastName);
Photo = null;
Contract = null;
} // EmployeeClass()
// --> "heavy" constructor
EmployeeClass
(
int AKey,
char* AFirstName,
char* ALastName,
Image* APhoto,
Blob* AContract
)
{
Key = AKey;
strcpy(FirstName, AFirstName;
strcpy(LastName, ALastName);
Photo = APhoto;
Contract = AContract;
} // EmployeeClass()
void Insert();
}; // class
void Test()
{
...
int AKey = 0;
char AFirstName[150];
char ALastName[150];
Image* APhoto = null;
Blob* AContract = null;
// --> calling "light" constructor
AKey = 1;
strcpy(AFirstName, "Mary");
strcpy(ALastName, "Thompson");
EmployeeClass* AEmployee = new EmployeeClass
(AKey, AFirstName, ALastName);
AEmployee->Insert();
// --> calling "heavy" constructor
AKey = 2;
strcpy(AFirstName, "John");
strcpy(ALastName, "Doe");
Image* APhoto = LoadPhoto();
Blob* AContract = LoadContract();
EmployeeClass* AEmployee = new EmployeeClass
(AKey, AFirstName, ALastName, APhoto, AContract);
AEmployee->Insert();
// --> calling "dummy" constructor,
// --> more work, but, more flexible
AKey = 1;
strcpy(AFirstName, "Mary");
strcpy(ALastName, "Thompson");
EmployeeClass* AEmployee = new EmployeeClass();
AEmployee->Key = AKey;
strcpy(AEmployee->FirstName, AFirstName);
strcpy(AEmployee->LastName, ALastName);
AEmployee->Photo = LoadPhoto();
AEmployee->Contract = LoadContract();
AEmployee->Insert();
...
} // void Test()
许多开发人员只使用"公共灯光构造函数",&拒绝拥有多个构造函数的想法。
[3]其他帮助
让我们暂时跳过"重"属性,稍后将继续。
这是许多C/C++开发人员不喜欢的建议,但我个人觉得在处理实体对象时非常有用。我使用"两步初始化"。
对于每个实体类,我声明一个构造函数,没有清除字段的参数,以及,添加一个虚拟方法,该方法具有非常特定的标识符,扮演构造函数的角色。
然后,我可以添加几个充当构造函数的虚拟方法,比如决定是否加载"重"属性。
所以,前面的例子变成了这样:
public class EmployeeClass
{
public:
bool F_EmployeeClass_IsReady;
public:
int Key;
char FirstName[150];
char LastName[150];
Image* Photo;
Blob* Contract;
public:
// --> only generic constructor
Employee()
{
F_EmployeeClass_IsReady = false;
Key = 0;
strcpy(FirstName, "");
strcpy(LastName, "");
Photo = null;
Contract = null;
} // EmployeeClass()
virtual bool IsReady()
{
return F_EmployeeClass_IsReady;
} // bool IsReady(...)
// --> works like "generic" constructor from previous example
virtual void Create()
{
Key = 0;
strcpy(FirstName, "");
strcpy(LastName, "");
Photo = null;
Contract = null;
F_EmployeeClass_IsReady = true;
} // void Create()
// --> works like "light" constructor from previous example
virtual void CreateLight
(
int AKey,
char* AFirstName,
char* ALastName
)
{
Key = AKey;
strcpy(FirstName, AFirstName);
strcpy(LastName, ALastName);
Photo = null;
Contract = null;
F_EmployeeClass_IsReady = true;
} // void CreateLight()
virtual void Destroy()
{
F_EmployeeClass_IsReady = false;
} // void Destroy()
// --> works like "heavy" constructor from previous example
virtual void CreateHeavy
(
int AKey,
char* AFirstName,
char* ALastName,
Image* APhoto,
Blob* AContract
)
{
Key = AKey;
strcpy(FirstName, AFirstName);
strcpy(LastName, ALastName);
Photo = APhoto;
Contract = AContract;
F_EmployeeClass_IsReady = true;
} // void CreateHeavy()
void Insert();
}; // class
void Test()
{
...
int AKey = 0;
char AFirstName[150];
char ALastName[150];
Image* APhoto = null;
Blob* AContract = null;
// --> calling "light" constructor
AKey = 1;
strcpy(AFirstName, "Mary");
strcpy(ALastName, "Thompson");
EmployeeClass* AEmployee = new EmployeeClass();
AEmployee->CreateLight(AKey, AFirstName, ALastName);
AEmployee->Insert();
AEmployee->Destroy();
delete AEmployee;
// --> calling "heavy" constructor
AKey = 2;
strcpy(AFirstName, "John");
strcpy(ALastName, "Doe");
Image* APhoto = LoadPhoto();
Blob* AContract = LoadContract();
EmployeeClass* AEmployee = new EmployeeClass();
AEmployee->CreateHeavy
(AKey, AFirstName, ALastName, APhoto, AContract);
AEmployee->Insert();
AEmployee->Destroy();
delete AEmployee;
// --> calling "dummy" constructor,
// --> more work, but, more flexible
AKey = 1;
strcpy(AFirstName, "Mary");
strcpy(ALastName, "Thompson");
EmployeeClass* AEmployee = new EmployeeClass();
AEmployee->Create();
AEmployee->Key = AKey;
strcpy(AEmployee->FirstName, AFirstName);
strcpy(AEmployee->LastName, ALastName);
AEmployee->Photo = LoadPhoto();
AEmployee->Contract = LoadContract();
AEmployee->Insert();
AEmployee->Destroy();
delete AEmployee;
...
} // void Test()
在前面的例子中,每个实体都是使用两个步骤创建的,一个是"伪"构造函数,另一个是互补方法,每种情况都不同,有一个有意义的标识符,在选择如何准备实体对象时很有用。
每个物体的破坏也是如此。
[4]重属性方法
最后,您可能需要添加一些方法,负责在需要时加载"重"属性。有时被显式调用,有时被自动调用。
public class EmployeeClass
{
public:
bool F_EmployeeClass_IsReady;
public:
int Key;
char FirstName[150];
char LastName[150];
Image* Photo;
Blob* Contract;
public:
// --> only generic constructor
Employee()
{
F_EmployeeClass_IsReady = false;
Key = 0;
strcpy(FirstName, "");
strcpy(LastName, "");
Photo = null;
Contract = null;
} // EmployeeClass()
virtual bool IsReady()
{
return F_EmployeeClass_IsReady;
} // bool IsReady(...)
void LoadPhoto();
void SavePhoto();
void LoadContract();
void SaveContract();
// --> works like "generic" constructor from previous example
virtual void Create()
{
Key = 0;
strcpy(FirstName, "");
strcpy(LastName, "");
Photo = null;
Contract = null;
F_EmployeeClass_IsReady = true;
} // void Create()
// --> works like "light" constructor from previous example
virtual void CreateLight
(
int AKey,
char* AFirstName,
char* ALastName
)
{
Key = AKey;
strcpy(FirstName, AFirstName);
strcpy(LastName, ALastName);
Photo = null;
Contract = null;
F_EmployeeClass_IsReady = true;
} // void CreateLight()
virtual void Destroy()
{
F_EmployeeClass_IsReady = false;
} // void Destroy()
// --> works like "heavy" constructor from previous example
virtual void CreateHeavy
(
int AKey,
char* AFirstName,
char* ALastName,
Image* APhoto,
Blob* AContract
)
{
Key = AKey;
strcpy(FirstName, AFirstName);
strcpy(LastName, ALastName);
Photo = APhoto;
Contract = AContract;
F_EmployeeClass_IsReady = true;
} // void CreateHeavy()
// --> works like "heavy" constructor from previous example
virtual void CreateAndLoad
(
int AKey,
char* AFirstName,
char* ALastName
)
{
Key = AKey;
strcpy(FirstName, AFirstName);
strcpy(LastName, ALastName);
LoadPhoto();
LoadContract;
F_EmployeeClass_IsReady = true;
} // void CreateAndLoad()
void Insert();
}; // class
void Test()
{
...
int AKey = 0;
char AFirstName[150];
char ALastName[150];
Image* APhoto = null;
Blob* AContract = null;
// --> calling "load" constructor
AKey = 1;
strcpy(AFirstName, "Mary");
strcpy(ALastName, "Thompson");
EmployeeClass* AEmployee = new EmployeeClass();
AEmployee->CreateLoad(AKey, AFirstName, ALastName);
AEmployee->Insert();
AEmployee->Destroy();
delete AEmployee;
...
} // void Test()
有了额外的方法,您可能会有一个忽略它们的[假]构造函数,而不加载"重"属性,一个调用它们的[假的]构造函数。或者,使用不使用它们的[fake]构造函数,&显式调用特定"heavy"属性的加载程序。
如果您从文件系统路径加载图像,这些也会有所帮助;将它们保存到数据库字段中,反之亦然,从数据库字段加载文件,&将它们保存到文件系统路径中。
干杯。
[EDIT]你的"flag"想法没问题。
在另一个答案中,我提供了一个替代解决方案,我个人认为这是一个更好的解决方案,但这并不意味着你的想法是错误的。
下面的例子,我确实在某些情况下应用了它,它是相同的"标志"思想,一种可能的实现,你可能想遵循,也可能不想遵循。
示例:
public class EmployeeClass
{
// --> logic fields
private:
bool F_HeavyLoaded;
// --> "entity" fields
public:
int Key;
char FirstName[150];
char LastName[150];
Image* Photo; // <- picture
Blob* Contract; // <- scanned contract
public:
// --> constructors
EmployeeClass
(
int AKey,
char* AFirstName,
char* ALastName
)
{
Key = AKey;
strcpy(FirstName, AFirstName;
strcpy(LastName, ALastName);
Photo = null;
Contract = null;
F_HeavyLoaded = false;
} // EmployeeClass()
// --> "heavy" constructor
EmployeeClass
(
int AKey,
char* AFirstName,
char* ALastName,
Image* APhoto,
Blob* AContract
)
{
Key = AKey;
strcpy(FirstName, AFirstName;
strcpy(LastName, ALastName);
Photo = APhoto;
Contract = AContract;
F_HeavyLoaded = true;
} // EmployeeClass()
public:
// --> "entity" methods
bool IsHeavyLoaded() { return F_HeavyLoaded; }
void Insert();
void Update();
void Delete();
}; // class
在本例中,有一个名为"F_IsHeavyLoaded"的私有标志字段,它只能在构造函数中修改。它以只读形式公开,使用函数。
干杯。
- pcap_handler回调仅在使用 NPCAP v0.9991 时包含空数据包
- 读取大文件(>2GB)(文本文件包含以太网数据)并通过不同参数随机访问数据的最佳方法是什么?
- 将包含二进制数据的 QByteArray 传递到按值运行
- std::bad_alloc 在大数据集的 Dijkstra 计算期间
- 使用 QT C++过滤大数据的最佳方式
- OpenSSL模块化最大数据类型大小的简单方法
- 对大数据使用提升filtering_streambuf
- std::fstream 需要很长时间才能将大数据写入.csv文件中
- QT:如何使用QT复制大数据
- 超过芯片组允许的最大数据类型.C++
- 写入单个大数据文件或多个小文件:哪个更快?
- 如何在以下C程序中计算运行时间内存和最大数据存储器使用情况
- spdlog 记录单个wchar_t字符串,其中包含包含 {..} 的数据
- 从TXT中取出大数据并对其进行排序
- 如何判断C++中的最大数据对齐要求
- 如何使用 mongo-cxx-driver 的 **insert** 函数将包含静态数据的多维数组插入到数据库中
- 如何在Qt中从txt文件加载大数据
- Websocket Poco无法接收大数据
- 创建包含大数据的共享库的最佳实践
- 多播大数据包包含多个客户端的所有信息,而单个数据包则包含目标客户端的信息