我有一个体系结构问题,更确切地说,是一个次优的情况。
对于适应性测试环境,存在由一系列定义方法更新的上下文,每种定义方法都定义不同的实体,即更改上下文。 为简单起见,此处的定义将只是整数,而上下文将是不断增长的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”中应用“定义”时,“ Less_Abstract_Test_Environment”中的VAR“上下文”仍未绑定,即为空。 在“ Less_Abstract_Test_Environment”(来自“ Abstract_Test_Environment”)中,定义“ definition”是“按需初始化”的,而变量“ context”则不是。
我想到的“解决方案”是合并“ Abstract_Test_Environment”和“ Less_Abstract_Test_Environment”。 这不是我想要的,因为它破坏了接口/目标与实现之间的自然分离,这是由两个特性实现的。
您看到更好的解决方案了吗? 我相信Scala可以做得更好。
简单的解决方案:不要在对象创建期间初始化它,除非您处于底层类中。 而是添加一个包含所有初始化代码的init
方法,然后在最底层的类(这是安全的,因为已经创建了所有父类)中或在创建对象的任何地方调用它。
整个过程的副作用是,您甚至可以覆盖子类中的初始化代码。
一种可能是使您的中级特征成为一类:
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)
}
您可以采用仅包含数据的白板的想法,该白板由许多仅包含逻辑(不包含数据!)的特征共享。 参见下面的一些未经测试的代码:
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
这样,您就可以将数据的定义与需要数据的位置分开,这基本上意味着您可以按任何顺序混合特征。 唯一的要求是白板(定义数据)是混合的第一个特征。