让咱们应用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)
那里有两个问题:
- JPA标准要求无参数构造函数。因而,能够ClassicalAccount应用空的IBAN创立实例。
- 没有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