Как смешивать черту с примером?
Учитывая черту MyTrait
:
trait MyTrait {
def doSomething = println("boo")
}
его можно смешать в классе с extends
или with
:
class MyClass extends MyTrait
Его также можно смешивать при создании экземпляра нового экземпляра:
var o = new MyOtherClass with MyTrait
o.doSomething
Но... может ли атрибут (или любой другой, если это имеет значение) добавить к существующему экземпляру?
Я загружаю объекты с использованием JPA в Java, и я хотел бы добавить к ним некоторые функции, используя черты. Возможно ли вообще?
Я хотел бы иметь возможность смешать в признаке следующим образом:
var o = DBHelper.loadMyEntityFromDB(primaryKey);
o = o with MyTrait //adding trait here, rather than during construction
o.doSomething
Ответы
Ответ 1
У меня есть идея для этого использования:
//if I had a class like this
final class Test {
def f = println("foo")
}
trait MyTrait {
def doSomething = {
println("boo")
}
}
object MyTrait {
implicit def innerObj(o:MixTest) = o.obj
def ::(o:Test) = new MixTest(o)
final class MixTest private[MyTrait](val obj:Test) extends MyTrait
}
вы можете использовать этот признак, как показано ниже:
import MyTrait._
val a = new Test
val b = a :: MyTrait
b.doSomething
b.f
для вашего примера кода:
val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait
o.doSomething
Надеюсь, это поможет вам.
ОБНОВЛЕНО
object AnyTrait {
implicit def innerObj[T](o: MixTest[T]):T = o.obj
def ::[T](o: T) = new MixTest(o)
final class MixTest[T] private[AnyTrait](val obj: T) extends MyTrait
}
но этот шаблон имеет некоторое ограничение, вы не можете использовать какой-то неявный вспомогательный метод, который уже определен.
val a = new Test
a.f
val b = a :: AnyTrait
b.f1
b.f
val c = "say hello to %s" :: AnyTrait
println(c.intern) // you can invoke String method
println(c.format("MyTrait")) //WRONG. you can't invoke StringLike method, though there defined a implicit method in Predef can transform String to StringLike, but implicit restrict one level transform, you can't transform MixTest to String then to StringLike.
c.f1
val d = 1 :: AnyTrait
println(d.toLong)
d.toHexString // WRONG, the same as above
d.f1
Ответ 2
Существующий объект времени выполнения в JVM имеет определенный размер в куче. Добавление признака к нему означало бы изменение его размера в куче и изменение его подписи.
Таким образом, единственный способ пойти - это сделать какое-то преобразование во время компиляции.
Состав смеси в Scala происходит во время компиляции. Компилятор потенциально может создать оболочку B вокруг существующего объекта A с тем же типом, который просто перенаправляет все вызовы на существующий объект A, а затем смешивается с признаком T от B. Это, однако, не реализовано. Это может быть вопросом, когда это было бы возможно, поскольку объект A может быть экземпляром конечного класса, который не может быть расширен.
Таким образом, композиция mixin невозможна в существующих объектных экземплярах.
ОБНОВЛЕНО:
В связи с умным решением, предложенным Гогол-Шаном, и обобщая его для работы с любым признаком, это насколько я понял. Идея состоит в том, чтобы извлечь общую функциональность mixin в черту DynamicMixinCompanion
. Затем клиент должен создать объект-компаньон, расширяющий DynamicMixinCompanion
для каждого признака, для которого он хочет иметь динамическую функциональность mixin. Этот объект-компаньон требует определения создания анонимного объекта-объекта (::
).
trait DynamicMixinCompanion[TT] {
implicit def baseObject[OT](o: Mixin[OT]): OT = o.obj
def ::[OT](o: OT): Mixin[OT] with TT
class Mixin[OT] protected[DynamicMixinCompanion](val obj: OT)
}
trait OtherTrait {
def traitOperation = println("any trait")
}
object OtherTrait extends DynamicMixinCompanion[OtherTrait] {
def ::[T](o: T) = new Mixin(o) with OtherTrait
}
object Main {
def main(args: Array[String]) {
val a = "some string"
val m = a :: OtherTrait
m.traitOperation
println(m.length)
}
}
Ответ 3
Я обычно использовал implicit
для смешивания нового метода с существующим объектом.
См., если у меня есть код, как показано ниже:
final class Test {
def f = "Just a Test"
...some other method
}
trait MyTrait {
def doSomething = {
println("boo")
}
}
object HelperObject {
implicit def innerObj(o:MixTest) = o.obj
def mixWith(o:Test) = new MixTest(o)
final class MixTest private[HelperObject](obj:Test) extends MyTrait
}
а затем вы можете использовать метод MyTrait
с уже существующим тестом объекта.
val a = new Test
import HelperObject._
val b = HelperObject.mixWith(a)
println(b.f)
b.doSomething
в вашем примере, вы можете использовать вот так:
import HelperObject._
val o = mixWith(DBHelper.loadMyEntityFromDB(primaryKey));
o.doSomething
Я рассматриваю синтаксис префекта, чтобы определить этот объект HelperObject:
trait MyTrait {
..some method
}
object MyTrait {
implicit def innerObj(o:MixTest) = o.obj
def ::(o:Test) = new MixTest(o)
final class MixTest private[MyTrait](obj:Test) extends MyTrait
}
//then you can use it
val a = new Test
val b = a :: MyTrait
b.doSomething
b.f
// for your example
val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait
o.doSomething
Ответ 4
Как насчет неявного класса? Мне кажется легче для меня по сравнению с другими ответами с окончательным внутренним классом и функцией "mixin".
trait MyTrait {
def traitFunction = println("trait function executed")
}
class MyClass {
/**
* This inner class must be in scope wherever an instance of MyClass
* should be used as an instance of MyTrait. Depending on where you place
* and use the implicit class you must import it into scope with
* "import mypackacke.MyImplictClassLocation" or
* "import mypackage.MyImplicitClassLocation._" or no import at all if
* the implicit class is already in scope.
*
* Depending on the visibility and location of use this implicit class an
* be placed inside the trait to mixin, inside the instances class,
* inside the instances class' companion object or somewhere where you
* use or call the class' instance with as the trait. Probably the
* implicit class can even reside inside a package object. It also can be
* declared private to reduce visibility. It all depends on the structure
* of your API.
*/
implicit class MyImplicitClass(instance: MyClass) extends MyTrait
/**
* Usage
*/
new MyClass().traitFunction
}