我有一个架构问题,更准确地说,是一个次优的情况。
对于自适应测试环境,存在由一系列定义方法更新的上下文,每个定义方法定义不同的实体,即改变上下文。为简单起见,这里的定义只是整数,而上下文是Seq [Int]。
trait Abstract_Test_Environment {
def definition(d: Int): Unit
/* Make definitions: */
definition(1)
definition(2)
definition(3)
}
这个想法现在由持有当前背景的连续改变的“var”实现:
trait Less_Abstract_Test_Environment extends Abstract_Test_Environment {
/* Implement the definition framework: */
var context: Context = initial_context
val initial_context: Context
override def definition(d: Int) = context = context :+ d
}
由于必须在应用“定义”之前设置上下文,因此无法通过结束类中的变量赋值来设置它:
class Concrete_Test_Environment extends Less_Abstract_Test_Environment {
context = Seq.empty
}
中间的“initial_context”是必需的,但是普通的覆盖也不能完成这项工作:
class Concrete_Test_Environment extends Less_Abstract_Test_Environment {
override val initial_context = Seq.empty
}
唯一可行的解决方案似乎是早期初始化,这很可能是为此功能创建的目的:
class Concrete_Test_Environment extends {
override val initial_context = Seq.empty
} with Less_Abstract_Test_Environment
然而,我们的设置仍然失败,因为当“Abstract_Test_Environment”中应用“definition”时,“Less_Abstract_Test_Environment”中的VAR“context”仍未绑定,即为null。在“Less_Abstract_Test_Environment”(来自“Abstract_Test_Environment”)中,def“定义”是“按需初始化”,而var“context”则不是。
我想出的“解决方案”是合并“Abstract_Test_Environment”和“Less_Abstract_Test_Environment”。这不是我想要的,因为它破坏了界面/目标和实现的自然分离,这已经由两个特征实现。
你看到更好的解决方案了吗?我相信Scala可以做得更好。
答案 0 :(得分:1)
简单解决方案:除了处于底层课程外,不要在创建对象时初始化对象。相反,添加一个init
方法,该方法包含所有初始化代码,然后在最底层的类中调用它(这是安全的,因为已经创建了所有父类)或者创建了对象。
整个事情的副作用是你甚至可以覆盖子类中的初始化代码。
答案 1 :(得分:1)
一种可能性是让你的中间特质成为一个阶级:
abstract class Less_Abstract_Test_Environment(var context: Context = Seq.empty) extends Abstract_Test_Environment {
override def definition(d: Int) = context = context :+ d
}
您现在可以将其子类化,并将不同的初始上下文作为参数传递给构造函数。
你可以在"具体的"等级,如果你更喜欢中间体作为特征:
trait Less_Abstract_Test_Environment extends Abstract_Test_Environment {
var context: Context
override def definition(d: Int) = context = context :+ d
}
class Concrete_Test_Environment(override var context: Context = Seq.empty) extends Less_Abstract_Test_Environment
虽然使用功能方法会更好:context
应该是val
,而definion
应该采用之前的值,然后返回新值:
trait Abstract {
type Context
def initialContext: Context
val context: Context = Range(1, 4)
.foldLeft(initialContext) { case (c, n) => definition(c, n) }
def definition(context: Context, n: Int): Context
}
trait LessAbstract extends Abstract {
override type Context = Seq[Int]
override def definition(context: Context, n: Int) = context :+ n
}
class Concrete extends LessAbstract {
override def initialContext = Seq(0)
}
答案 2 :(得分:0)
你可以使用白板的概念,它只包含数据,这些数据由许多只包含逻辑(而不是数据!)的特征共享。请参阅下面一些未经测试的袖口代码:
trait WhiteBoard {
var counter: Int = 0
}
trait Display {
var counter: Int
def show: Unit = println(counter)
}
trait Increment {
var counter: Int
def inc: Unit = { counter = counter + 1 }
}
然后你写这样的单元测试:
val o = new Object with Whiteboard with Display with Increment
o.show
o.inc
o.show
这样,您可以将数据的定义与需要数据的地方分开,这基本上意味着您可以按任何顺序混合特征。唯一的要求是白板(定义数据)是混合的第一个特征。