让咱们应用Spring,Java和Kotlin来查看OOP是否真的在企业级别上失去撑持,以及在进行我的项目时必须思考的各种折衷。

本周,在与我在一所高等学校开设的Java课程无关的研讨会上,我留神到由学生编写的代码基本上还能够-齐全是程序性的。实际上,只管Java语言自吹自as是一种面向对象的语言,但找到由企业中的业余开发人员开发的此类代码并不少见。例如,JavaBean标准与OOP的次要原理之一封装间接矛盾。

另一个例子是在Java EE和Spring应用程序中同样宽泛应用的控制器,服务和DAO架构。在这种状况下,实体通常是 贫乏的,而所有业务逻辑都位于服务层中。只管这自身还不错,但该设计将状态与行为离开,并位于真正的OOP的另一端。

Java EE和Spring框架都强制执行此分层设计。例如,在春天,有一个为每个这样的层一个正文:@Controller,@Service,和@Repository。在Java EE世界中,只能使@EJB实例(服务层)具备事务性。

这篇文章旨在尝试和谐OOP范例和分层体系结构。我将应用Spring框架来强调我的观点,因为我对此更加相熟,然而我置信雷同的办法能够用于纯Java EE应用程序。

一个简略的用例

让咱们有一个简略的用例:从IBAN编号中找到具备相干余额的关联帐户。在规范设计中,这可能相似于:

@RestControllerclass ClassicAccountController(private val service: AccountService) {    @GetMapping("/classicaccount/{iban}")    fun getAccount(@PathVariable("iban") iban: String) = service.findAccount(iban)}@Serviceclass AccountService(private val repository: ClassicAccountRepository) {    fun findAccount(iban: String) = repository.findOne(iban)}interface ClassicAccountRepository : CrudRepository<ClassicAccount, String>@Entity@Table(name = "ACCOUNT")class ClassicAccount(@Id var iban: String = "", var balance: BigDecimal = BigDecimal.ZERO)

那里有两个问题:

  1. JPA标准要求无参数构造函数。因而,能够ClassicalAccount应用空的IBAN创立实例。
  2. 没有IBAN的验证。须要残缺往返数据库以查看IBAN是否无效。

留神:不,没有货币。这是一个简略的例子,还记得吗?

合规

为了恪守无参数构造函数JPA束缚(并且因为咱们应用Kotlin),能够生成综合构造函数。这意味着能够通过反射拜访构造函数,但不能间接调用构造函数。

<plugin>    <artifactId>kotlin-maven-plugin</artifactId>    <groupId>org.jetbrains.kotlin</groupId>    <version>${kotlin.version}</version>    <configuration>        <compilerPlugins>            <plugin>jpa</plugin>        </compilerPlugins>    </configuration>    <dependencies>        <dependency>            <groupId>org.jetbrains.kotlin</groupId>            <artifactId>kotlin-maven-noarg</artifactId>            <version>${kotlin.version}</version>        </dependency>    </dependencies></plugin>

留神:如果您应用Java,那么运气不好,我不晓得有什么方法能够解决。

增加验证

在层体系结构中,服务层是搁置业务逻辑(包含验证)的显著地位:

@Serviceclass AccountService(private val repository: ClassicAccountRepository) {    fun findAccount(iban: String): Account? {        checkIban(iban)        return repository.findOne(iban)    }    fun checkIban(iban: String) {        if (iban.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")    }}

为了更合乎OOP,咱们必须决定是否容许有效的IBAN编号。齐全禁止它更容易。

@Entity@Table(name = "ACCOUNT")class OopAccount(@Id var iban: String, var balance: BigDecimal = BigDecimal.ZERO) {    init {        if (iban.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")    }}

然而,这意味着咱们必须首先创立一个OopAccount实例来验证IBAN,即便余额实际上不是0,余额也为0。同样,对于空的IBAN,代码与模型不匹配。更蹩脚的是,要应用存储库,咱们必须拜访OopAccount外部状态:

repository.findOne(OopAccount(iban).iban)

面向对象的设计更敌对

改善代码状态须要对类模型进行大量批改,将IBAN和帐户离开,以便能够验证前者并拜访后者。IBAN类既充当帐户的入口点又充当PK。

@Entity@Table(name = "ACCOUNT")class OopAccount(@EmbeddedId var iban: Iban, var balance: BigDecimal)class Iban(@Column(name = "iban") val number: String,           @Transient private val repository: OopAccountRepository) : Serializable {    init {        if (number.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")    }    val account        @JsonIgnore        get() = repository.findOne(this)}

请留神,返回的JSON构造将不同于下面返回的构造。如果这是一个问题,则自定义Jackson以取得所需后果非常容易。

通过这种新设计,控制器须要进行一些更改:

@RestControllerclass OopAccountController(private val repository: OopAccountRepository) {    @GetMapping("/oopaccount/{iban}")    fun getAccount(@PathVariable("iban") number: String): OopAccount {        val iban = Iban(number, repository)        return iban.account    }}

这种办法的惟一毛病是,须要将存储库注入到控制器中,而后将其显式传递给实体的构造函数。

最初的接触

如果将存储库在创立时主动注入到实体中,那就太好了。好的,Spring通过面向方面的编程使这成为了可能,只管这不是一个十分出名的性能。它须要执行以下步骤:

向应用程序增加AOP性能

无效地增加AOP依赖关系非常简单,只需将相干的启动器依赖关系增加到POM:

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-aop</artifactId></dependency>

而后,必须将应用程序配置为应用它:

@SpringBootApplication@EnableSpringConfiguredclass OopspringApplication

更新实体

首先必须将实体设置为注入指标。依赖注入将通过主动拆卸实现。
而后,必须将存储库从结构函数参数移至字段。
最初,数据库获取逻辑能够移到实体中:

@Configurable(autowire = Autowire.BY_TYPE)class Iban(@Column(name = "iban") val number: String) : Serializable {    @Transient    @Autowired    private lateinit var repository: OopAccountRepository    init {        if (number.isBlank()) throw IllegalArgumentException("IBAN cannot be blank")    }    val account        @JsonIgnore        get() = repository.findOne(this)}

留神:请记住,场注入是邪恶的。

纵横编织

有两种编织方面的办法,即编译时编织或加载时编织。我抉择后者,因为它更容易配置。它是通过规范Java代理实现的。

首先,须要将其作为运行时依赖项增加到POM中:

<dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-agent</artifactId>    <version>2.5.6</version>    <scope>runtime</scope></dependency>

而后,必须应用代理配置Spring Boot插件:

<plugin>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-maven-plugin</artifactId>    <configuration>        <agent>${settings.localRepository}/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar</agent>    </configuration></plugin>

最初,必须对应用程序进行相应的配置:

@EnableLoadTimeWeavingclass OopspringApplication

进而?

当然,该示例省略了设计的重要局部:如何更新帐户余额。分层办法对此有一个解决办法,但这不是面向对象的。考虑一下,一个帐户的余额会发生变化,因为有另一个帐户的转帐。能够将其建模为:

fun OopAccount.transfer(source: OopAccount, amount: BigDecimal) { ... }

有教训的开发人员应该会看到一些事务管理需要。我将实现留给有动机的读者。下一步将是缓存值,因为每次读取和写入都拜访数据库会升高性能。

论断

我想提出几点。

首先,题目问题的答案是必定的“是”。后果是真正的OOP代码,同时仍应用所谓的企业级框架(即Spring)。

然而,迁徙到与OOP兼容的设计会带来一些开销。咱们不仅依赖于现场注入,还必须通过加载时织入引入AOP。第一个是单元测试期间的阻碍,第二个是您相对不须要每个团队应用的技术,因为它们会使应用程序变得更加简单。这仅是一个简略的例子。

最初,这种办法有一个微小的毛病:大多数开发人员都不相熟它。无论其劣势如何,首先必须“限度”他们具备这种思维形式。这可能是持续应用传统分层体系结构的起因。

参考:《2020最新Java根底精讲视频教程和学习路线!》

原文链接:https://blog.csdn.net/weixin_46699878/article/details/113428250