Scala不可变的对象和特征与val字段

我想仅使用不可变对象构建我的域模型。 但我也想使用val字段的特征,并将一些功能转移到特征。 请看下面的例子:

trait Versionable {
 val version = 0
 def incrementVersion = copy(version=version+1)
}

不幸的是,这样的代码不起作用 - 特性Versionable的复制方法是未知的。

我认为,为每个特质和班级生成复制方法会很好。 这种方法应该创建对象的浅拷贝,并使用与原始对象相同的类型返回对象,其中给定的字段被修改为根据传递给方法的参数。

所以在下面的例子中:

class Customer(val name: String) extends Versionable {
 def changeName(newName: String) = copy(name = newName)
}

val customer = new Customer("Scot")

customer.changeName("McDonnald")应该返回一个对象实例Customer(version = 0, name = "McDonnald")

customer.incrementVersion还应该返回一个对象实例Customer(version = 1, name = "Scot")

据我所知目前在Scala中缺乏这样的功能不允许使用不可变的类和特性,而不会污染具有特征字段的类构造函数。 在我的示例中,我不想将名为参数的版本引入Customer类,因为版本处理的功能我想要封装在Versionable特征中。

我知道案例类中复制方法的功能以及使用默认参数在类中编写自己的复制方法的能力 - 但我认为这个功能并不能解决我的问题,因为在特征中不可能使用这种复制方法。 现有功能的另一个缺点是使用复制方法的父类返回父类而不是实际复制的对象的类。

我的问题:

1)你有没有想法如何以优雅的方式处理上面的例子。 我对Scala很陌生,所以也许已经有了很好的解决方案。 在我看来,优雅的解决方案应该具备以下特点

  • 不应该使用反射

  • 不应该使用序列化

  • 应该快

  • 在编译时应该是可验证的

  • 2)您对编写编译器插件为我的上述示例生成复制方法代码有什么看法? 使用编译器插件可以做到这一点吗? 你有任何例子或提示如何做到这一点?


    最干净的解决方案可能是从Versionable删除一些实现逻辑,并将其从类型堆栈中压入一个案例类(其中copy方法可供您使用)。 给版本属性一个默认值来完成设计。

    trait Versioned {
      def version : Int
      def nextVersion = version + 1 
    }
    
    case class Customer(name: String, version : Int = 0) extends Versioned {
      def withName(newName: String) = copy(name = newName, version = nextVersion)
    }
    

    如果你愿意,你也可以在版本编号的某个地方定义一个类型别名:

    type Version = Int
    val initialVersion = 0
    
    trait Versioned {
      def version : Version
      def nextVersion = version + 1 
    }
    
    case class Customer(name: String, version : Version = initialVersion)
    extends Versioned {
      def withName(newName: String) = copy(name = newName, version = nextVersion)
    }
    

    这是另一种解决方案,就像OP的代码一样,不起作用。 但是,它可以提供更简单(更普遍有用)的扩展语言的起点。

    trait Versionable[T] {
       self: { def copy(version: Int): T } =>
       val version = 0
       def incrementVersion = copy(version = version + 1)
    }
    
    case class Customer(name: String, override val version: Int) 
          extends Versionable[Customer] {
       def changeName(newName: String) = copy(name = newName)
    }
    

    如果编译器将客户类的copy方法识别为符合Versionable的自我类型注释中定义的方法(这看起来是使用命名参数和默认参数的自然方式),则该代码将起作用。


    虽然你说过,你不想使用案例类。 以下是使用它们的解决方案:

    case class Version(number: Int) {
      override def toString = "v" + number
      def next = copy(number+1)
    }
    
    case class Customer(name: String, version: Version = Version(0)) {
      def changeName(newName: String) = copy(newName)
      def incrementVersion = copy(version = version.next)
    }
    

    现在你可以做到这一点:

    scala> val customer = new Customer("Scot")
    customer: Customer = Customer(Scot,v0)
    
    scala> customer.changeName("McDonnald")
    res0: Customer = Customer(McDonnald,v0)
    
    scala> customer.incrementVersion
    res1: Customer = Customer(Scot,v1)
    
    scala> customer // not changed (immutable)
    res2: Customer = Customer(Scot,v0)
    
    链接地址: http://www.djcxy.com/p/82689.html

    上一篇: Scala immutable objects and traits with val fields

    下一篇: What is the Scala equivalent to a Java builder pattern?