16

函数式编程促进了不可变类和引用透明性。

领域驱动设计由值对象(不可变)和实体(可变)组成。

我们应该创建不可变实体而不是可变实体吗?

假设,项目使用 Scala 作为主要语言,如果我们正在处理并发,我们如何将实体编写为案例类(因此是不可变的)而不冒陈旧状态的风险?

什么是好的做法?保持实体可变(var字段等...)并避免案例类的出色语法?

4

2 回答 2

15

您可以在 Scala 中有效地使用不可变实体,并避免可变字段的恐惧以及源自可变状态的所有错误。使用不可变实体可以帮助您实现并发,不会让事情变得更糟。您之前的可变状态将成为一组转换,它将在每次更改时创建一个新的引用。

但是,在您的应用程序的某个级别,您需要有一个可变状态,否则您的应用程序将毫无用处。这个想法是在你的程序逻辑中尽可能地推动它。让我们以银行账户为例,它可能会因利率和 ATM 取款或存款而发生变化。

您有两种有效的方法:

  • 您公开可以修改内部属性的方法并管理这些方法的并发性(实际上很少)

  • 您使所有类不可变,并用可以更改帐户的“经理”包围它。

由于第一个非常简单,我将详细介绍第一个。

case class BankAccount(val balance:Double, val code:Int)

class BankAccountRef(private var bankAccount:BankAccount){
   def withdraw(withdrawal) = {
       bankAccount = bankAccount.copy(balance = bankAccount.balance - withdrawal)
       bankAccount.balance
   }
}

这很好,但是天哪,你仍然被并发管理所困扰。好吧,Scala 为您提供了一个解决方案。这里的问题是,如果您将对 BankAccountRef 的引用共享给您的后台作业,那么您将不得不同步调用。问题是您正在以次优的方式进行并发。

实现并发的最佳方式:消息传递

如果另一方面,不同的作业不能直接调用 BankAccount 或 BankAccountRef 上的方法,而只是通知它们需要执行一些操作怎么办?那么,你就有了一个 Actor,这是 Scala 中最喜欢的并发处理方式。

class BankAccountActor(private var bankAccount:BankAccount) extends Actor {

    def receive {
        case BalanceRequest => sender ! Balance(bankAccount.balance)
        case Withdraw(amount) => {
            this.bankAccount = bankAccount.copy(balance = bankAccount.balance - amount)
        }
        case Deposit(amount) => {
            this.bankAccount = bankAccount.copy(balance = bankAccount.balance + amount)

        }

    }
}

此解决方案在 Akka 文档中进行了广泛描述:http: //doc.akka.io/docs/akka/2.1.0/scala/actors.html。这个想法是您通过向其邮箱发送消息来与 Actor 进行通信,并且这些消息按接收顺序进行处理。因此,如果使用此模型,您将永远不会有并发缺陷。

于 2013-02-11T14:33:25.600 回答
10

这是一个意见问题,它不像你想象的那么具体。

如果您真的想接受 FP,我会为您的所有域对象采用不可变的路线,并且永远不会对它们进行任何行为。

也就是有人将上述称为服务模式,在这种模式下,行为和状态总是分开的。这在 OOP 中避开了,但在 FP 中很自然。

这也取决于您的域是什么。对于有状态的东西,比如 UI 和视频游戏,OOP 有时会更容易。对于网站或 REST 等硬核后端服务,我认为服务模式更好。

除了经常提到的并发性之外,我喜欢不可变对象的两个非常好的地方是它们更可靠地缓存,并且它们对于分布式消息传递(例如,protobuf over amqp)也非常有用,因为意图非常明确。

同样在 FP 中,人们通过创建“语言”或“对话”也就是 DSL(Builders、Monads、Pipes、Arrows、STM等)来对抗可变到不可变的桥梁,使您能够变异然后转换回不可变的领域。上面提到的服务使用 DSL 进行更改。这比你想象的更自然(例如 SQL 就是一个例子“对话”)。另一方面,OOP 更喜欢拥有可变域并利用语言的现有程序部分。

于 2013-02-11T14:29:28.063 回答