Scala: как обогатить класс свойством

#scala

#scala

Вопрос:

Я пытаюсь обогатить класс пользовательским свойством:

 class MyClass {

   def printNumber(n: Int) = {
     println(n)
   }
}

class MyClassWithName(myClass: MyClass, val name: String) {}

implicit def myClassWithName(myClass: MyClass, name: String) = new MyClassWithName(myClass, name)
 

Затем я пытаюсь получить доступ к пользовательскому свойству name следующим образом…

 val c = myClassWithName(new MyClass, "jonny")
c.printNumber(5)
 

… но он не компилируется:

 value printNumber is not a member of MyClassWithName.
 

Я что-то упускаю? Tx.

Комментарии:

1. Я совершенно запутался, пытаясь понять, чего вы пытаетесь достичь. Возможно, вы можете добавить в свой вопрос общее утверждение о том, какую функциональность вы ожидаете. Я предполагаю, что вы хотите иметь возможность обрабатывать экземпляр MyClassWithName is, если бы он был MyClass ?

2. Вы обращаетесь не к свойству name , а к методу printNumber . Почему вы делаете метод неявным, который вы вызываете явно? MyClassWithName не имеет метода printNumber , так как же вы ожидаете, что он будет компилироваться?

3. почему вы не используете c.MyClass.printNumber(5)? Поскольку метод printNumber находится внутри класса MyClass.

4. То, что говорит Джаспер, верно. Я знаю, что мог бы реализовать оболочку… Мне просто интересно, есть ли лучшие альтернативы.

Ответ №1:

Если вы хотите добавить только name свойство и ничего больше, вы могли бы сделать это следующим образом:

 object MyClassPlus {
  private val map = scala.collection.mutable.Map.empty[MyClass,String]
}

implicit class MyClassPlus(val myClass: MyClass) extends AnyVal {

  def name = MyClassPlus.map(myClass) 

  def name_= (value: String) {
    MyClassPlus.map(myClass) = value
  }
}
 

Затем вы можете получить доступ к name, как если бы он был членом MyClass :

 scala> val c = new MyClass
c: MyClass = MyClass@78e312af

scala> c.name = "foo"
c.name: String = foo

scala> c.name
res4: String = foo
 

Обратите внимание, что при таком подходе equals метод MyClass должен проверять равенство ссылок. В противном Map случае необходимо изменить, чтобы вместо этого использовать равенство ссылок.


Если вы хотите иметь возможность добавлять много свойств к объекту во время выполнения, и вы можете изменить определение класса, который вы можете расширить Dynamic .

 import scala.language.dynamics

class MyClass extends Dynamic {

  private val map = scala.collection.mutable.Map.empty[String,String]

  // the type of value doesn't necessarily have to be String
  def updateDynamic(name: String)(value: String) {
    map(name) = value
  }

  def selectDynamic(name: String) = map(name)
}
 

Затем вы можете добавлять и получать доступ к таким свойствам, как это:

 scala> val c = new MyClass
c: MyClass = MyClass@50f85a5f

scala> c.name = "foo"
c.name: String = foo

scala> c.name
res6: String = foo
 

Если вы не можете изменить определение класса, вы можете попытаться выполнить какое-то неявное преобразование, подобное этому:

 object MyClassPlus {
  private val map = scala.collection.mutable.Map.empty[(MyClass,String),String]
}

implicit class MyClassPlus(val myClass: MyClass) extends AnyVal {

  def update(name: String, value: String) {
    MyClassPlus.map((myClass, name)) = value
  }

  def apply(name: String) = MyClassPlus.map((myClass, name))
}
 

Затем вы можете добавлять и получать доступ к свойствам, но с менее интуитивно понятным синтаксисом:

 scala> val c1 = new MyClass
c1: MyClass = MyClass@635b4736

scala> val c2 = new MyClass
c2: MyClass = MyClass@1be2e9e5

scala> c1("name") = "foo"

scala> c2("name") = "bar"

scala> c1("name")
res2: String = foo

scala> c2("name")
res3: String = bar
 

Комментарии:

1. Большое вам спасибо за ваше исчерпывающее объяснение! Это именно то, что я искал 🙂

2. Я должен добавить, что если вы реализуете свойство name как a Map и планируете создавать много экземпляров MyClass , у вас могут возникнуть проблемы, потому что они не будут собираться мусором. Было бы лучше использовать что-то вроде WeakHashMap вместо реализации по умолчанию Map .

Ответ №2:

Вы, вероятно, хотели обратного:

 class Wrapper(original: OriginalClass) {
  def printNumber(n: Int) = println(n)
  def printName = println(original.name) // I added this method to justify wrapping of original
}

class OriginalClass(val name: String)

implicit def toWrapper(original: OriginalClass) = new Wrapper(original)

val x = new OriginalClass("johny")
// x: OriginalClass = OriginalClass@21788153

x.printName
// johny

x.printNumber(1)
// 1
 

Обратите внимание, что более новые версии scala имеют более краткий синтаксис с implicit class комбинацией

Ответ №3:

Ваш класс-оболочка MyClassWithName содержит myClass и name в качестве своих членов. Итак, чтобы получить доступ к printNumber функции в классе MyClass , вам сначала нужно получить доступ к объекту myClass .

Вы можете написать метод получения, использовать @BeanProperty аннотацию или, что еще лучше, использовать класс case следующим образом:

 case class MyClassWithName(myClass: MyClass, val name: String)

val c = myClassWithName(new MyClass, "jonny")
c.myClass.printNumber(5)
 

Это скомпилировало бы.