#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)
Это скомпилировало бы.