在我之前的工作中,程序处理依赖于存储在数据库中的数据。所以数据库模型主导了运行时程序的数据结构。因此,使用主键值作为对其他对象的引用是很方便的。例如:
考虑到一家公司通过互联网销售书籍,我们有类(class)
Book
,Order
和Customer
.Book
类包含有关图书的不同属性和唯一标识符,例如 ISBN。Customer
类包含公司向客户运送书籍所需的所有数据(以及更多),例如电子邮件地址。客户对象也有一个唯一的持久 ID 来标识它们。因此
Order
类包含两个关系引用int isbn;
(图书 ID)和int customer_id;
.
在这个例子中,订单类方法不需要访问客户数据或书籍数据,因此订单类不需要依赖它们。
现在考虑另一个类来编写和发送订单的电子邮件确认:
class OrderMailer { // Customer index std::map<int, Customer *> customers; ... // we have a function that sends email with low level parameters void sendEmail(const std::string& mailAddress, const std::string& body); // and we have another method that simply sends the email for a given order void sendEmail(const Order& order); };
sendEmail(const Order& order)
方法需要客户的电子邮件地址,因此需要从其标识符中获取对象。这就是为什么我们有一张 map ,所以地址将像这样访问:const std::string& target = customers[order.customer_id]->emailAddress; // not found test omitted for reading.
我毫无疑问地使用了这种引用方式,因为对象/记录 ID 是识别整个公司对象的方式(代码、日志、与其他 IT 的讨论)。运行时数据结构总是反射(reflect)数据库模型。可能不是一个可靠的论点,但在数据库世界和运行时世界(C++、Python、JavaScript)之间切换非常有帮助。
我不再在那家公司工作,但在处理持久记录时保留了这种编程方式。我不确定使用逻辑方式来引用对象而不是语言提供的方式(指针或 C++ 引用)是否正确。这么说对我来说听起来真的很糟糕。
优点:
- 运行时数据结构反射(reflect)了底层关系数据模型(更易于理解)。
- 避免无用的类耦合(例如,
Order
不依赖于Customer
和Book
类)。 - 唯一标识符可以是易于阅读的字符串。
缺点:
- 不使用语言的基本特征(指针和引用)来执行此操作听起来很糟糕。
- 需要字典/索引(映射)来访问逻辑引用对象的数据。
我不确定这是一件好事。是否有决定是否使用此方法的规则?
最佳答案
tl;dr:只要指针和引用在您的程序中有机地有意义,就使用它们。
数据库表记录的设计原理不同于编程中使用的类。
- (关系)数据库的结构是表格;程序中使用的实体 - 不是;至少,一般情况下不会。
- 数据库表通常希望遵循“规范形式”等原则。
- 数据库是使用声明式 查询而不是程序来访问的。在幕后,数据库管理系统实际计算的内容可能不同(例如,涉及索引而不是在多个关键字段上工作)。事实上,数据库甚至可能是 columnar,在这种情况下您甚至不保存单独的记录,例如按顺序排列的所有书籍的标题。
在程序中,
- 您经常想提及“我刚刚使用的那个对象”。
- 有时您只需要识别单个数据库表记录的部分信息;或 更多 标识单个表记录的内容;或计算数据或信息,这些数据或信息甚至不在数据库中。
- (特定于 C++)存在继承和模板化等机制,这是关系数据库不允许的
因此,原则上,您不能假设程序中类的选择应该与数据库中的类选择相同。通过数据库键字段识别实体也不总是合理的。
后一种选择也可能对性能产生负面影响,正如您自己提到的:如果您在内存中有一个 Book 类对象的容器,并且其他一些数据结构使用 ISBN 引用它们,您将需要执行一个每次访问时都在该容器中搜索 ISBN。这,而不是使用指针,或检索您需要的部分书籍数据,一次,而无需稍后遵循引用。 (当然,只有当这种按 ISBN 查找的次数非常多时,这才是一个问题;不要费心去优化很少运行的代码的性能。)
关于你的优点和缺点:
Pros :
- If there is an underlying relational data model, the runtime data structures reflect the data model and things are easier to understand
关系数据模型不是“底层”——除非您编写类只是为了从数据库中读取和写入。此外,数据库物理存储在 DBMS 内存中的内容不一定反射(reflect)关系结构。
- This avoid useless coupling of classes (In the example, Order does not depends on Customer and Book classes)
您的示例表明耦合通常并非无用,而是适当的。定义图书顺序可能需要知道图书是什么,客户是什么。
使用 forward declarations 也可以避免与指针和引用耦合。 :
class Book; class Customer; class Order { // ... const Book& book; // assuming there's just one book per order here const Customer& customer; // ... }
- Unique identifiers can be strings that are very human friendly to read
当你阅读一个程序时,你不会阅读任何这些字符串,你只会阅读 std::string book_title;
作为类成员,这并不比 const Book& related_book
.
Cons :
- Why not using the basic fundamental features of the language that are pointers and references to do this? thats sounds a bad approach.
当语言结构在您的程序中有机地有意义时,请使用它们。有时使用其他结构(例如数组中的索引)是有意义的。视具体情况而定。
- We need to use dictionaries/index (maps) every-time we want to access data of logically referenced objects
确实。
关于c++ - 使用数据库模型(键)来引用运行时对象,好主意还是坏主意?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15245750/