如何使 class 包装一个在 Scala 中不可变的不可变集合?

How to make a class wrapping an immutable collection immutable in Scala?

在之前的 中,我询问了一种使容器 class 包装线程安全的不可变集合的惯用方法。我收到的所有答案都涉及使用各种风格的 read/write 锁或同步,这不是我想要的。

让我问一个不同的问题。如何使包装不可变容器的以下 class 不可变? add/remove 方法需要 return 一个新的 MyContainer class 实例适当地改变,但我不太明白如何去做......

class MyContainer[A] {

  // method that returns a new MyContainer that includes the additional thing...
  def add(thing: A): MyContainer[A] = {
    ???
  }

  def filter(p: A => Boolean): Option[Iterable[A]] = {
    val filteredThings = backingStore.values.filter(p)
    if (filteredThings.isEmpty) None else Some(filteredThings)
  }

  // method that returns a new MyContainer that does not include the thing with given uuid
  def remove(uuid: UUID): MyContainer[A] = {
    ???
  }

  @ volatile private[this] var backingStore = immutable.HashMap.empty[UUID, A]

}

想法?

编辑: 作为对评论的回应,一种可能的解决方案类似于以下...

class MyContainer[A](val backingStore: immutable.HashMap[UUID, A]) {

  def add(thing: A): MyContainer[A] = {
    new MyContainer(backingStore + (thing.uuid -> thing))
  }

  def filter(p: A => Boolean): Option[Iterable[A]] = {
    val filteredThings = backingStore.values.filter(p)
    if (filteredThings.isEmpty) None else Some(filteredThings)
  }

  def remove(uuid: UUID): MyContainer[A] = {
    new MyContainer(backingStore - uuid)
  }

}

...backingStore 不再是私有的(但可以将 private 放入构造函数中)。更多想法?

您需要一种方法来构建一个新的 MyContainer,它已经包含一些元素并且最好保持相同的 UUID。这意味着您基本上需要一个初始化 backingStore 的构造函数。但是,如果您不想以任何方式公开它,您可以将构造函数设为私有,并提供一个仅允许外部代码创建空集合的重载构造函数(假设)。 backingStore 可以简单地移到私有构造函数中。

class MyContainer[A] private (backingStore: HashMap[UUID, A]) {

  def this() = this(HashMap.empty[UUID, A])

  def add(thing: A): MyContainer[A] = {
    val uuid: UUID = UUID.randomUUID() // or however the UUID is generated
    new MyContainer(backingStore + ((uuid, thing)))
  }

  def remove(uuid: UUID): MyContainer[A] =
    new MyContainer(backingStore - uuid)

}

scala> val container = new MyContainer[String]()

scala> container.add("a").add("b").add("c")
res2: MyContainer[String] = MyContainer@4a183d02

不过,这完全取决于您是否希望在 API 中公开。我不确定你要用 filter 做什么,所以我把它从我的例子中删除了。