问题回顾
正如@leanne 所说,您正在建模一个Subscription
其专长是,比如说,MonthlySubscription
和ComplimentarySubscription
(给他们一个这个答案的名字)。
您知道订阅可能会过期:
- 对于 a
MonthlySubscription
,当用户没有支付当月的订阅费时会发生这种情况
- 对于 a
ComplimentarySubscription
,到期日期是在分配给用户时分配的
如您所见, anExpirationDate
是 any 的基本属性Subscription
,但在每种情况下存储它的方式都不同。如果第一种情况您必须根据最后一个事件计算它,在后者中您可以直接检索它。
处理数据库中的继承
因此,要将这个示例模型映射到数据库模式,您可以使用Martin Fowler 的企业应用程序架构模式一书中描述的类表继承模式。这是它的意图:
“表示类的继承层次结构,每个类都有一个表”。
基本上,您将拥有一个表,其中包含在类之间共享的属性,并且您将在单独的表中存储特定于每个类的属性。
记住这一点,让我们回顾一下您提出的选项:
- 有另一个
complimentary_subscription
以用户 ID 作为外键的表吗?
有一个单独的表来存储ComplimentarySubscription
特定的细节听起来不错,但如果你不将这个表与subscription
表关联起来,你最终可能会得到一个同时拥有 aMonthlySubscription
和 a的用户ComplimentarySubscription
。它的外键应该指向subscription
表,该表告诉您用户是否有订阅(并且您必须为每个用户强制执行最多一个订阅)。
- 为他们记录一个特殊的“订阅”
subscription
?
是的,您必须记录用户每月或免费订阅。但是,如果您正在考虑诸如记录金额为零的特殊订阅之类的事情,那么您正在寻找与您当前设计相匹配的解决方案,而不是为其寻找正确的模型(可能也可能不是)。
- 或者将另一列添加到他们的用户行中以获取
is_complimentary
和complimentary_expires_date
?
我个人不喜欢这个,因为你把信息放在不属于它的地方。考虑到这一点,您将在哪里存储免费订阅的到期日期(请记住,您正在计算每月订阅的到期日期,而不是存储到期日期)?似乎所有这些信息都在为自己的“家”而哭泣。此外,如果稍后您需要添加新类型的订阅,该表将开始混乱。
如果这样做,则每次更改时都必须处理数据同步subscription_event
(在每月订阅的情况下)。通常我会尽量避免这种数据重复的情况。
样品溶液
在添加新类型的订阅时,为了支持可扩展性,我会做的是让表存储和subscription
之间的共享详细信息,添加一个列键,让您区分一行与哪种订阅相关。MonthlySubscripton
ComplimentarySubscription
type
然后,将特定于每个订阅类型的详细信息存储在其自己的表中,并引用父subscription
行。
为了检索数据,您需要一个对象来负责实例化Subscription
给定行的正确类型的type
列值subscription
。
您可以查看“企业应用程序架构模式”一书中的模式,以获得有关如何定义type
列值、如何使用映射器对象进行Subscription
实例化等方面的进一步帮助。
type
2012 年 1 月 3 日更新:定义和处理列的替代方法
这是一个更新,以澄清@enoinoc 在评论中发布的以下问题:
特别是对于建议的type
列,这可能是指向一个Plans
表的外键,该表描述了不同类型的订阅,例如在它们到期前多少个月没有付款。这听起来合乎逻辑吗?
可以在Plans
表格中包含该信息,只要它不是不需要编辑的静态信息。如果是这种情况,请不要过度概括您的解决方案并将该知识放在相应的Subscription
子类中。
关于外键方法,我可以想到这样的一些缺点:
- 请记住,您的目标是了解表
Subscription
中每一行要使用的子类Plans
。如果你得到的只是一个外键值(比如一个整数),你必须编写代码将该值映射到要使用的类。这意味着你需要额外的工作:)
- 如果您必须做不必要的额外工作,维护可能会很痛苦:每次添加新计划时,您都必须记住在映射代码中硬编码它的外键值。
- 如果数据库导出/导入操作不当,外键可能会发生变化。如果发生这种情况,您的映射代码将不再工作,您将不得不再次部署您的软件(或者,至少,更改的部分)。
建议的解决方案
我要做的是放入表中的type
列中Plans
。该列将包含知道如何Subscription
从特定行构建权限的类的名称。
但是:为什么我们需要一个对象来构建每种类型的Subscription
? 因为您将使用来自不同表 (subscription_event
和complimentary_subscription
) 的信息来构建每种类型的对象,并且隔离和封装该行为总是好的。
让我们看看Plans
表格可能是什么样子:
-- 计划表 --
身份证 | 姓名 | 类型 | 其他栏目...
1 | 月刊 | MonthlySubscriptionMapper
|
2 | 免费 | ComplimentarySubscriptionMapper
|
每个都SubscriptionMapper
可以定义一个方法,该方法Subscription MapFrom(Row aRow)
从数据库中获取一行并为您提供Subscription
子类的正确实例(MonthlySubscription
或ComplimentarySubscription
在示例中)。
最后,要获取type
列中指定的映射器的实例(不使用讨厌的if
或case
语句),您可以从列的值中获取类名,并通过使用反射创建该类的实例。