在不使用var的情况下,如何使scala中的静态值无效并在以后将其再次设置为新值

brgchamk  于 2021-06-09  发布在  Cassandra
关注(0)|答案(1)|浏览(323)

我已经做了一个工厂方法,它应该启动一个数据库( cassandra )并连接到它或应返回现有会话。到数据库的连接是静态字段。

class EmbeddedCassandraManager {

  def getCassandra() = {
    if(EmbeddedCassandraManager.cassandraConnection.isDefined) //return existing instance
      {
        (EmbeddedCassandraManager.testCassandra,EmbeddedCassandraManager.cassandraConnection)
      }
     else {
      EmbeddedCassandraManager.startCassandra()
    }
  }

  def closeCassandra() = {

    EmbeddedCassandraManager.closeCassandra()
  }
}

object  EmbeddedCassandraManager {
  val factory = new EmbeddedCassandraFactory
//can I do the logic without using var?

  var (testCassandra,cassandraConnection) = startCassandra()

  def closeCassandra() = {
    cassandraConnection.get.close()
    cassandraConnection = None
    testCassandra.stop()
  }

  def startCassandra():(Cassandra,Option[CassandraConnection]) = {
    val testCassandra = factory.create()
    testCassandra.start()
    val cassandraConnectionFactory:DefaultCassandraConnectionFactory = new DefaultCassandraConnectionFactory();

    val localCassandraConnection:Option[CassandraConnection] = try{
      val connection = cassandraConnectionFactory.create(testCassandra)
      Some(connection)
    }catch{
      case exception:Throwable => {
        throw exception
      }
    }
    this.cassandraConnection = localCassandraConnection

    (testCassandra,this.cassandraConnection)
  }
}

我能够创建逻辑的唯一方法是使用 var 对于 cassandraConnection . 有没有一个模式我可以用来避免使用 var ?
在一次测试中,我不得不停下来 cassandra 测试数据库未运行时连接是否未建立。这会使现有连接过时。没有 var ,无法将值设置为 None 使连接无效,并在再次建立数据库连接后将其设置为新值。
创建这种逻辑的功能方法是什么?我需要连接的静态值,以便只创建一个连接,并且我需要一种方法来检查该值是否过时。

pbwdgjma

pbwdgjma1#

易变性通常是不可避免的,因为它是我们构建的系统的固有属性。然而,这并不意味着我们必须在代码中使用可变变量。
通常有两种主要方法可以处理涉及可变状态的情况:
将可变状态推送到程序外部的存储库中。
典型的例子是“标准”数据库(如果状态需要持久化)和内存存储(如果状态在程序生命周期内存在)。无论何时从这样的存储中获取值,都会将其视为不可变的值。易变性仍然存在,但不在程序中,这使其更容易推理。
有些人批评这种思维方式,说“你没有解决任何问题,你只是把它变成别人的问题”,事实上这是真的。我们让数据库为我们处理可变性。为什么不?这就是数据库的设计目的。此外,易变性的主要问题是关于它的推理,我们不会对数据库的内部实现进行推理。因此,将可变性从一个服务推到另一个服务,确实就像扔烫手山芋,但将其推到专门为其设计的外部系统是完全正确的。
然而,尽管如此,这对您的情况并没有帮助,因为将数据库连接对象存储在外部存储器中并不是很优雅。我要说的是第二点。
使用状态单子。
如果“monad”这个词给你带来了一些启示,那就假装我说了“使用” State “(其实这是个很简单的概念,不需要大字)。我将使用 State 在cats库中可用,但它也存在于其他fp库中。 State 是从某个现有状态到某个新状态和某个生成值的函数:

S => (S, V)

通过从一个现有状态到一个新的状态,我们实现了“状态突变”。
例1:
下面是一些使用整数状态的代码,该状态将递增1,并在每次状态更改时生成一个字符串值:

import cats.data.State

val s: State[Int, String] = State((i: Int) => (i + 1, s"Value: $i"))

val program = for {
  produced1 <- s
  _ = println(produced1) // Value: 42
  produced2 <- s
  _ = println(produced2) // Value: 43
  produced3 <- s
  _ = println(produced3) // Value: 44
} yield "Done."

program.run(42).value

这就是要点。
例2:
为了完整性,这里有一个更大的示例,它演示了一个与您类似的用例。
首先,让我们介绍一个简化的 CassandraConnection (这只是为了以身作则;真实对象将来自cassandra库,因此我们自己的代码中不存在可变性)。

class CassandraConnection() {
  var isOpen: Boolean = false
  def connect(): Unit = isOpen = true
  def close(): Unit = isOpen = false
}

我们应该如何定义国家?可变对象显然是 CassandraConnection ,用于理解的结果值可以是一个简单的 String .

import cats.data.State

type DbState = State[CassandraConnection, String]

现在让我们定义一些函数来使用现有的 CassandraConnection 对象。

val openConnection: DbState = State(connection => {
  if (connection.isOpen) {
    (connection, "Already connected.")
  } else {
    val newConnection = new CassandraConnection()
    newConnection.connect()
    (newConnection, "Connected!")
  }
})

val closeConnection: DbState = State(connection => {
  connection.close()
  (connection, "Closed!")
})

val checkConnection: DbState =
  State(connection => {
    if (connection.isOpen) (connection, "Connection is open.")
    else (connection, "Connection is closed.")
  })

最后,让我们在主程序中使用这些函数:

val program: DbState =
  for {
    log1 <- checkConnection
    _ = println(log1) // Connection is closed.
    log2 <- openConnection
    _ = println(log2) // Connected!
    log3 <- checkConnection
    _ = println(log3) // Connection is open.
    log4 <- openConnection
    _ = println(log4) // Already connected.
    log5 <- closeConnection
    _ = println(log5) // Closed!
    log6 <- checkConnection
    _ = println(log6) // Connection is closed.
  } yield "Done."

program.run(new CassandraConnection()).value

我知道这不是您可以复制/粘贴到项目中并使其正常工作的确切代码,但我想给出一个稍微更一般的答案,这可能会让其他读者更容易理解。我相信,只要有人玩弄一下,你就能把它塑造成你自己的解决方案。只要你的主程序是一个关于理解的 State 级别,您可以轻松地打开和关闭连接,并(重新)使用相同的连接对象。
我们用这个解决方案到底取得了什么成果?为什么这比有一个可变的 CassandraConnection 价值?
一件大事是我们实现了引用透明性,这就是为什么这个模式很好地适合于函数式编程范式,而标准的可变性却不适合。由于这个答案已经有点长了,我将向您介绍cats文档,它将更详细地解释整个过程,并演示使用cats的好处 State 非常好。

相关问题