您见过那些具有许多属性的巨大物体吗? 这些域对象由于不希望从数据库检索太多信息而在其中使用延迟加载? 我敢打赌你有这种令人怀疑的快乐。
今天,我想与大家分享我对它们的印象- 使用延迟加载应该被视为一种代码味道!
让我解释一下自己:
- 延迟加载意味着有时您不需要某个对象的某些属性。 这些属性将在不同的上下文中是必需的。 这是否意味着您要根据上下文构建不同的对象?
- 使用此对象的功能肯定知道太多。 它知道对象的API,并且此API还包含需要未加载属性的方法。 很好,不是吗?
- 您必须记住每个地方需要什么,不需要什么…
- …而且,更糟糕的是,您必须记住您可能使用的功能以及特定位置不支持的方法。
如果您还不够,请让我详细说明。
延迟加载如何工作
简而言之, 延迟加载允许您在加载父级时不加载子级。 仅当您明确要求时才加载它们。
它是如何工作的? 让我们看一个简单的例子:
class User {private final Name name;@OneToMany(fetch = FetchType.LAZY)private List<Role> roles;@OneToMany(fetch = FetchType.LAZY)private List<Subscription> subscriptions;// Some more attributes and methods
}
此类的定义告诉您什么? FetchType.LAZY对我们意味着什么? 这为我们提供了包含用户角色和订阅的列表的信息,除非我们明确要求此类数据,否则它们不会填充数据。
什么是有界上下文?
受限上下文是域驱动开发中的主要模式之一。 通过将它们分为不同的上下文,它可以帮助您使用大型域模型。 由于这个原因,您的域对象变得更小,应用程序的业务逻辑变得更容易理解。
但是……为什么呢?
在前面的段落中,我写了User类的定义告诉我们的内容。 到现在为止,一切都与机制有关。 现在我们可以走得更远。
让我们再来看一下我们的课:
class User {private final Name name;@OneToMany(fetch = FetchType.LAZY)private List<Role> roles;@OneToMany(fetch = FetchType.LAZY)private List<Subscription> subscriptions;// Some more attributes and methods
}
除了已经提到的内容之外,您能告诉我更多有关此对象的信息吗?
我们知道我们正在使用其对象在可能需要但不一定需要角色的地方使用的类。 可能需要订阅但不一定要订阅的地方。 名称始终是必需的。
我们知道在我们的应用程序/环境中有一些功能/位置需要这些属性,而在某些地方这些属性是无用的。
但是……我们必须遍历代码才能找到那些地方。 这需要时间和精力。 不幸的是,我们还有机会错过一些地方。
我们所知道的...我们所不知道的...
知道在哪里和需要什么会更好吗? 当然可以! 问题是:如何实现?
让我们对示例进行简短分析:
class User {private final Name name;@OneToMany(fetch = FetchType.LAZY)private List<Role> roles;@OneToMany(fetch = FetchType.LAZY)private List<Subscription> subscriptions;// Some more attributes and methods
}
我们已经知道一些事情:
- 名称始终是必需的。
- 有时我们需要角色。
- 有时我们需要订阅。
根据这些信息,我们可以添加另一件事– 我们知道我们并不总是需要所有这些信息 。 也许听起来有些琐碎,但这也很重要。
这就是信息。 现在是未知的时候了:
- 在哪里我们既需要角色又需要订阅?
- 在不同的地方需要角色和订阅吗?
- 有没有我们不需要的地方?
- 是否取决于上下文需要什么属性?
未知数的问题在于,我们必须遍历代码才能找到答案。 但这还不是问题的终点。 当您最终找到这些位置时,没有方法或变量或任何可重命名的信息,不会在一段时间内丢失此信息。 下次您将不得不重复该工作。
让我们改进代码
由于上一段中列出了未知数,因此更改现有代码(真正的代码)和我们正在使用的代码并不容易。 这就是为什么我建议您在考虑延迟加载之后立即进行此更改。 这是最便宜的改进的正确时机。
好的,但是如何从示例中改进代码?
首先要做的是找到未知数的答案。 没有这些答案,我们就无法前进。 在我们的案例中,我将假设我们认识到三种不同的情况:
- 身份验证和授权是我们需要用户名及其角色的地方。
- 我们在处理报告发送的地方需要用户名及其订阅。
- 在我们应用程序的其他区域,我们既不需要角色也不需要订阅。
现在,我们可以重构User类并将其拆分为更容易理解的内容:
class AuthUser {private final Name name;private List<Role> roles;// Some more attributes and methods
}class ReportUser {private final Name name;private List<Subscription> subscriptions;// Some more attributes and methods
}class ApplicationUser {private final Name name;// Some more attributes and methods
}
现在我们有了三个类,而不是一个,但是我们的代码中也有更多信息。 我们无需遍历代码即可找出所需内容和位置。 打开类的定义就足够了
下一步是什么?
不幸的是,要在您的域中显示状态,您必须付出很多努力。 为什么? 主要是因为未知。 应用程序越大,获取所有信息的难度就越大。 这就是为什么我鼓励您在考虑将延迟加载作为解决方案之后立即拆分类。
如果您的域中已经有延迟加载的引用,则应仅重构已经使用的部分。 您将使更改的风险和进行更改所需的工作最小化。 无论如何,代码将变得更具描述性。
祝好运!
翻译自: https://www.javacodegeeks.com/2017/01/lazy-loading-code-smell.html