面向对象编程是否与企业环境兼容?

本周,在与我在一所高等学校开设的Java课程有关的研讨会上,我注意到学生编写的代码主要-完全是程序性的。 实际上,尽管Java语言自吹自as是一种面向对象的语言,但找到由企业中的专业开发人员开发的此类代码并不少见。 例如, JavaBean规范与OOP的主要原理之一封装直接矛盾。

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

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

这篇文章旨在尝试调和OOP范例和分层体系结构。 我将使用Spring框架来强调我的观点,因为我对此更加熟悉,但是我相信相同的方法也可以用于纯Java EE应用程序。

一个简单的用例

让我们有一个简单的用例:从IBAN编号中找到具有相关余额的关联帐户。 在标准设计中,可能看起来像这样:

@RestController
classClassicAccountController(privatevalservice:AccountService){

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

@Service
classAccountService(privatevalrepository:ClassicAccountRepository){
    funfindAccount(iban:String)=repository.findOne(iban)
}

interfaceClassicAccountRepository:CrudRepository<ClassicAccount,String>

@Entity
@Table(name="ACCOUNT")
classClassicAccount(@Idvariban:String="",varbalance:BigDecimal=BigDecimal.ZERO)

那里有两个问题:

  1. JPA规范要求无参数构造函数。 因此,可以使用空的IBAN创建ClassicalAccount实例。
  2. 没有IBAN的验证。 需要完整的数据库往返行程以检查IBAN是否有效。
是的,没有货币。 这是一个简单的例子,还记得吗?

合规

为了符合no-args构造函数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,那么运气不好,我不知道有任何解决方案。

添加验证

在层架构中,服务层是放置业务逻辑(包括验证)的显而易见的位置:

@Service
classAccountService(privatevalrepository:ClassicAccountRepository){
    funfindAccount(iban:String):Account?{
        checkIban(iban)
        returnrepository.findOne(iban)
    }

    funcheckIban(iban:String){
        if(iban.isBlank())throwIllegalArgumentException("IBAN cannot be blank")
    }
}

为了更符合OOP,我们必须决定是否允许无效的IBAN编号。 完全禁止它更容易。

@Entity
@Table(name="ACCOUNT")
classOopAccount(@Idvariban:String,varbalance:BigDecimal=BigDecimal.ZERO){
    init{
        if(iban.isBlank())throwIllegalArgumentException("IBAN cannot be blank")
    }
}

但是,这意味着我们必须首先创建OopAccount实例以验证OopAccount即使余额实际上不是0,余额也为0。同样,对于空的IBAN,代码与模型不匹配。 更糟糕的是,要使用存储库,我们必须访问OopAccount内部状态:

repository.findOne(OopAccount(iban).iban)

面向对象的设计更友好

改善代码状态需要对类模型进行大量修改,将IBAN和帐户分开,以便可以验证前者并访问后者。 IBAN类既充当入口点,又充当帐户的PK。

@Entity
@Table(name="ACCOUNT")
classOopAccount(@EmbeddedIdvariban:Iban,varbalance:BigDecimal)

classIban(@Column(name="iban")valnumber:String,
           @Transientprivatevalrepository:OopAccountRepository):Serializable{

    init{
        if(number.isBlank())throwIllegalArgumentException("IBAN cannot be blank")
    }

    valaccount
        @JsonIgnore
        get()=repository.findOne(this)
}
请注意,返回的JSON结构将不同于上面返回的结构。 如果这是一个问题,可以很容易地自定义Jackson以获取所需的结果。

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

@RestController
classOopAccountController(privatevalrepository:OopAccountRepository){

    @GetMapping("/oopaccount/{iban}")
    fungetAccount(@PathVariable("iban")number:String):OopAccount{
        valiban=Iban(number,repository)
        returniban.account
    }
}

这种方法的唯一缺点是,需要将存储库注入到控制器中,然后将其显式传递给实体的构造函数。

最后一点

如果将存储库在创建时自动注入到实体中,那就太好了。 嗯,Spring通过面向方面的编程使这成为可能-尽管这不是一个非常知名的功能。 它需要执行以下步骤:

向应用程序添加AOP功能

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

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

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

@SpringBootApplication
@EnableSpringConfigured
classOopspringApplication
更新实体
  1. 首先必须将该实体设置为注入目标。 依赖注入将通过自动装配完成。
  2. 然后,将存储库从构造函数参数移至字段。
  3. 最后,数据库获取逻辑可以移到实体中:
    @Configurable(autowire=Autowire.BY_TYPE)
    classIban(@Column(name="iban")valnumber:String):Serializable{
    
        @Transient
        @Autowired
        privatelateinitvarrepository:OopAccountRepository
    
        init{
            if(number.isBlank())throwIllegalArgumentException("IBAN cannot be blank")
        }
    
        valaccount
            @JsonIgnore
            get()=repository.findOne(this)
    }
记住,注入电场是邪恶的。
纵横编织

有两种方法可以将方面编织到中,即编译时编织或加载时编织。 我选择的是以后的配置要容易得多。 它是通过标准Java代理实现的。

  1. 首先,需要将其作为运行时依赖项添加到POM中:
    <dependency>
        <groupId> org.springframework </groupId>
        <artifactId> spring-agent </artifactId>
        <version> 2.5.6 </version>
        <scope> runtime </scope>
    </dependency>
  2. 然后,必须使用代理配置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>
  3. 最后,必须对应用程序进行相应的配置:
    @EnableLoadTimeWeaving
    classOopspringApplication

然后?

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

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

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

结论

我想提出几点。

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

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

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

我在Google上搜索了一些科学研究,证明OOP在可读性和可维护性方面更好:我没有发现。 我将非常感谢指针。
可以在Github上以Maven格式找到此文章的完整源代码。

翻译自: https://blog.frankel.ch/oop-compatible-enterprise-context/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值