OOP与企业环境兼容吗?

让我们使用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编号中找到具有相关余额的关联帐户。在标准设计中,这可能类似于:

@RestController
class ClassicAccountController(private val service: AccountService) {

    @GetMapping("/classicaccount/{iban}")
    fun getAccount(@PathVariable("iban") iban: String) = service.findAccount(iban)
}

@Service
class AccountService(private val repository: ClassicAccountRepository) {
    fun findAccount(iban: String) = repository.findOne(iban)
}

interface ClassicAccountRepository : CrudRepository

@Entity
@Table(name = "ACCOUNT")
class ClassicAccount(@Id var iban: String = "", var balance: BigDecimal = BigDecimal.ZERO)

那里有两个问题:

  1. JPA规范要求无参数构造函数。因此,可以ClassicalAccount使用空的IBAN创建实例。
  2. 没有IBAN的验证。需要完整往返数据库以检查IBAN是否有效。

注意:不,没有货币。这是一个简单的例子,还记得吗?

合规

为了遵守无参数构造函数JPA约束(并且由于我们使用Kotlin),可以生成综合构造函数。这意味着可以通过反射访问构造函数,但不能直接调用构造函数。


    kotlin-maven-plugin
    org.jetbrains.kotlin
    ${kotlin.version}
    
        
            jpa
        
    
    
        
            org.jetbrains.kotlin
            kotlin-maven-noarg
            ${kotlin.version}
        
    

注意:如果您使用Java,那么运气不好,我不知道有什么办法可以解决。

添加验证

在层体系结构中,服务层是放置业务逻辑(包括验证)的明显位置:

@Service
class 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以获得所需结果非常容易。

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

@RestController
class 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:


    org.springframework.boot
    spring-boot-starter-aop

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

@SpringBootApplication
@EnableSpringConfigured
class 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中:


    org.springframework
    spring-agent
    2.5.6
    runtime

然后,必须使用代理配置Spring Boot插件:


    org.springframework.boot
    spring-boot-maven-plugin
    
        ${settings.localRepository}/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar
    

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

@EnableLoadTimeWeaving
class OopspringApplication

进而?

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

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

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

结论

我想提出几点。

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

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

最后,这种方法有一个巨大的缺点:大多数开发人员都不熟悉它。无论其优势如何,首先必须“限制”他们具有这种思维方式。这可能是继续使用传统分层体系结构的原因。

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

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

你可能感兴趣的:(OOP与企业环境兼容吗?)