Method inheritance in immutable classes
I am stumbling on something that I hope is a bit of a basic issue. Probably its because I am new to Scala, and probably I am still missing some important concepts.
I am trying to program in an FP fashion, and data classes which do not need to have a mutable state are immutable, with some transformation methods to create new objects to update them if needed. However, I am struggling when it comes to maintaining the return types of this method when I have traits and general inheritance in place. I wish to avoid messy type casts or things like that as much as possible, because this is still a learning experience for me.
See this example here, where I have an immutable class extending some trait. The update
method is intended to change the data, call some method of the actual class (which is abstract in the trait) and return a new instance of the same class updated to the new data. One could map this roughly to the Template pattern.
trait MyTrait
{
val someDataVal : Integer;
def update(newDataVal) : MyTrait = {
//some logic takes place here, which is common
abstractUpdate(newDataVal)
}
//some logic takes place, specific to the implementation class
def abstractUpdate(newDataVal : Integer) : MyTrait
}
class MyClass(dataVal : Integer) extends MyTrait
{
override val someDataVal = dataVal
def abstractUpdate(newDataVal : Integer) : MyClass = {
//some class specific logic here ........
MyClass(newDataVal)
}
def someOtherFunction() : Integer = {
//some logic here .....
}
}
I obviously do not wish to copy and paste update()
into MyClass
I want it to stay in the trait so that I can use it by any class extending it. However if I try to call it, what I get is an object of type MyTrait
, and so I cannot call someOtherFunction()
on it.
What is the right Scala approach to achieve this kind of OO reuse and still have my code clean?
UPDATE
Please take note of the places where I put //some logic takes place here
, that means I might have some code there which I want to be centralised in the trait and not copied and pasted in each concrete class extending it. This is just a skeleton to illustrate the problem. Thanks for your time.
UPDATE
Code example based on the answer provided by wheaties. The issue is with the return this
.
trait MyTrait[T <: MyTrait[T]]{
def update(newValue: Int): T = {
if (newValue == 0)
return this; //this creates a type mismatch
else
concreteUpdate(newValue)
}
def concreteUpdate(value : Int) : T
}
class MyClass(value: Int) extends MyTrait[MyClass]
{
override def concreteUpdate(value : Int) = new MyClass(value)
}
I've answered a similar question before and the comment by @GaborBakos is spot on. If you'd like to be able to do similar things to what you might find with the map
method of TraverseableLike
then you need to do the following:
trait MyTrait[T <: MyTrait[T]]{
def update(newValue: Int): T
}
which basically is a type definition which depends on itself! Hence, the return type of update
is T
. Then:
class MyClass(value: Int) extends MyTrait[MyClass]{
def update(newValue: Int) = new MyClass(newValue)
}
which should work since T
is MyClass
.
Side Note:
Don't put val
in a trait. Instead, make it a def
. That way, you don't run into initialization ordering issues with anyone that wants to extend your class. If you don't follow this advice, then you can run into situations where a very NOT null
field is being treated as a null
.
上一篇: 有关测试和界面污染的问题
下一篇: 不可变类中的方法继承