c++ - 使用数据库模型(键)来引用运行时对象,好主意还是坏主意?

标签 c++ reference coding-style relational-database idioms

在我之前的工作中,程序处理依赖于存储在数据库中的数据。所以数据库模型主导了运行时程序的数据结构。因此,使用主键值作为对其他对象的引用是很方便的。例如:

  1. 考虑到一家公司通过互联网销售书籍,我们有类(class) Book , OrderCustomer .

    • Book类包含有关图书的不同属性和唯一标识符,例如 ISBN。

    • Customer类包含公司向客户运送书籍所需的所有数据(以及更多),例如电子邮件地址。客户对象也有一个唯一的持久 ID 来标识它们。

    • 因此 Order类包含两个关系引用 int isbn; (图书 ID)和 int customer_id; .

    在这个例子中,订单类方法不需要访问客户数据或书籍数据,因此订单类不需要依赖它们。

  2. 现在考虑另一个类来编写和发送订单的电子邮件确认:

    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 不依赖于 CustomerBook 类)。
    • 唯一标识符可以是易于阅读的字符串。
  • 缺点:

    • 不使用语言的基本特征(指针和引用)来执行此操作听起来很糟糕。
    • 需要字典/索引(映射)来访问逻辑引用对象的数据。

我不确定这是一件好事。是否有决定是否使用此方法的规则?

最佳答案

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)
  1. 您的示例表明耦合通常并非无用,而是适当的。定义图书顺序可能需要知道图书是什么,客户是什么。

  2. 使用 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/

相关文章:

rust - 为什么要借这个?

ios - 打开 GL ES 1 和 iOS 5 引用

C++ 返回对动态分配对象的引用

functional-programming - 二郎:干得好

C++ , 字符串数组

c++ - 计算将 n 划分为正整数之和的方法数 c++

c++ - 内部结构和外部结构的符号冲突,C++ vs C

c++ - 为什么不在 cstring.h 中的 strcmp 处使用常量指针

C++ "Best"参数传递方式

oop - 什么是间接对象表示法,为什么不好,如何避免呢?