构造函数必须是无代码的

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

构造函数中应该完成多少工作?在构造函数中进行一些计算然后封装结果似乎是合理的。这样,当对象方法需要结果时,我们就会准备好它们。听起来是个好方法?不,这不对。出于一个原因,这是一个坏主意:它阻止了对象的组合并使它们不可扩展。

杀死比尔:卷。 2 (2004) 昆汀·塔伦蒂诺

假设我们正在制作一个代表人名的界面:


 interface name {
  string first();
}


很简单,对吧?现在,让我们尝试实现它:


 interface name {
  string first();
}


这有什么问题?它更快,对吧?它只将名称分成几部分并将它们封装起来。那么,无论我们调用多少次 first() 方法,它都会返回相同的值,并且不需要再次进行拆分。然而,这是有缺陷的想法!让我告诉你正确的方法并解释:


 interface name {
  string first();
}


这是正确的设计。我可以看到你在微笑,所以让我证明我的观点。

不过,在我开始证明之前,让我请您阅读这篇文章:可组合装饰器与命令式实用方法。它解释了静态方法和可组合装饰器之间的区别。上面的第一个片段非常接近命令式实用方法,尽管它看起来像一个对象。第二个例子是一个真实的对象。

在第一个例子中,我们滥用了 new 运算符并将它变成了一个静态方法,它 现在就 为我们做所有的计算。这就是 命令式 编程的意义所在。在命令式编程中,我们立即进行所有计算并返回完全准备好的结果。在声明式编程中,我们试图尽可能长时间地延迟计算。

让我们尝试使用我们的 englishname 类:


 interface name {
  string first();
}


在此代码段的第一行中,我们只是创建一个对象的实例并将其标记为 name 。我们还不想访问数据库并从那里获取全名,将其拆分为多个部分,然后将它们封装在 name 中。我们只想创建一个对象的实例。这样的解析行为会对我们产生副作用,在这种情况下,会减慢应用程序的速度。如您所见,如果出现问题我们可能只需要 name.first() 并且我们需要构造一个异常对象。

我的观点是,在构造函数内部进行 任何 计算都是一种不好的做法,必须避免,因为它们是副作用,并且不是对象所有者所要求的。

您可能会问,在重新使用 name 期间的性能如何。如果我们创建一个 englishname 的实例,然后调用 name.first() 五次,我们最终将调用 string.split() 方法五次。

为了解决这个问题,我们创建了另一个类 a ,它将帮助我们解决这个“重用”问题:


 interface name {
  string first();
}


我正在使用 jcabi-aspects 中的 cacheable 注释,但您可以使用 java(或其他语言)中可用的任何其他缓存工具,例如 番石榴缓存


 interface name {
  string first();
}


但请不要让 cachedname 可变和延迟加载 — 这是一种反模式,我之前在 .

这就是我们的代码现在的样子:


 interface name {
  string first();
}


这是一个非常原始的例子,但我希望你明白了。

在这个设计中,我们基本上将对象分成两部分。第一个知道如何从英文名中获取名字。第二个知道如何在内存中缓存此计算的结果。现在,作为这些类的用户,我决定如何使用它们。我将决定是否需要缓存。这就是对象组合的全部内容。

让我重申,构造函数中唯一允许的语句是赋值。如果你需要在那里放其他东西,开始考虑重构——你的类肯定需要重新设计。

相关文章