关于java:OOP与企业环境兼容吗

31次阅读

共计 4932 个字符,预计需要花费 13 分钟才能阅读完成。

让咱们应用 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<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,那么运气不好,我不晓得有什么方法能够解决。

增加验证

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

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

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

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

@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 中:

<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>

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

@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

正文完
 0